UNIVERSIDADE FEDERAL DO PARANÁ
CE329 - APRENDIZAGEM DE MAQUINA
Profº: Eduardo Vargas Ferreira
Alunos: GRR20233876 – Leonardo Gonçalves Fischer
GRR20233881 – Raymundo Eduardo Pilz
O problema a ser abordado neste trabalho é a taxa de churn em uma instituição financeira. A taxa de churn, que se refere à perda de clientes ao longo do tempo, é uma métrica crítica para empresas de serviços financeiros, pois afeta diretamente a receita e a satisfação do cliente. O objetivo deste estudo é analisar e compreender os fatores que podem estar contribuindo para o churn de clientes e identificar estratégias para reduzi-lo.
Conhecer a taxa de churn de qualquer instituição é importante visto que para a empresa custa muito mais captar um novo cliente do que manter um que já possui. Compreender esses fatores é fundamental para tomar decisões informadas que permite a companhia desenvolver programas de leadade e implementar estratégias que aumentem a retenção de clientes.
https://www.kaggle.com/datasets/radheshyamkollipara/bank-customer-churn
Nesta seção, serão organizado todas as bibliotecas que serão utilizadas no projeto. O objetivo é manter estruturado as biblioteca armazenadas nesta seção para sua melhor visualização.
# Carregando as bibliotecas
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
sns.set(color_codes=True)
import warnings
warnings.filterwarnings('ignore')
import math
from tabulate import tabulate
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
from sklearn.model_selection import cross_val_score, KFold
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples
from sklearn.decomposition import PCA
from scipy.stats import f_oneway
from sklearn.manifold import TSNE
import plotly.express as px
from plotly.subplots import make_subplots
Inicialmente será carregado o conjunto de dados extraído do Kaggle, prepará-lo e transformá-lo para que os dados se tornem informações valiosas e possam ser usados efetivamente em nossa análise.
Primeiramente, realizaremos o carregamento do conjunto de dados e uma visualização inicial dos dados carregados.
## Carregando os dados
data = pd.read_csv('dataset\\Customer-Churn-Records.csv')
data.head()
| RowNumber | CustomerId | Surname | CreditScore | Geography | Gender | Age | Tenure | Balance | NumOfProducts | HasCrCard | IsActiveMember | EstimatedSalary | Exited | Complain | Satisfaction Score | Card Type | Point Earned | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 15634602 | Hargrave | 619 | France | Female | 42 | 2 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 | 1 | 2 | DIAMOND | 464 |
| 1 | 2 | 15647311 | Hill | 608 | Spain | Female | 41 | 1 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 | 1 | 3 | DIAMOND | 456 |
| 2 | 3 | 15619304 | Onio | 502 | France | Female | 42 | 8 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 | 1 | 3 | DIAMOND | 377 |
| 3 | 4 | 15701354 | Boni | 699 | France | Female | 39 | 1 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 | 0 | 5 | GOLD | 350 |
| 4 | 5 | 15737888 | Mitchell | 850 | Spain | Female | 43 | 2 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 | 0 | 5 | GOLD | 425 |
# Verificando o volume de dados do dataset
shape = data.shape
print("Observa-se que o conjunto de dados possui {} linhas e {} colunas.".format(shape[0], shape[1]))
Observa-se que o conjunto de dados possui 10000 linhas e 18 colunas.
Agora, realizaremos a verificação das colunas que contêm dados faltantes em nosso conjunto de dados. A identificação de variáveis com valores ausentes é fundamental, pois isso pode impactar nossa análise e determinar a necessidade de tratamento dos dados.
# Verificando colunas com dados faltantes
data_na = data.isnull().sum()
cols_na = data_na[data_na > 0]
if cols_na.empty:
print("Nenhuma coluna possui dados ausentes.")
else:
print(f"{len(cols_na)} coluna(s) possui(em) dados ausentes.")
print("Colunas com dados ausentes:")
for col, missing_count in cols_na.items():
print(f"{col}: {missing_count} valor(es) ausente(s)")
Nenhuma coluna possui dados ausentes.
# Representação gráfica
# Verificando colunas com dados faltantes
data_na = pd.DataFrame((data.isnull().sum() * 100 / data.shape[0])).reset_index()
# Gráfico
plt.figure (figsize = (16,5))
ax = sns.pointplot(x='index', y=0, data=data_na)
plt.xticks(rotation = 50,fontsize = 7)
plt.title("Porcentagem de dados faltantes")
plt.ylabel("Porcentagem")
plt.show()
Nesta etapa, realizaremos a verificação de dados duplicados. Embora seja improvável que tenhamos dados duplicados neste conjunto de dados, é uma etapa importante para garantir a integridade de nossa análise. Vamos verificar e, se necessário, remover qualquer entrada duplicada.
Para evitar interferências na detecção de dados duplicados, também excluiremos a coluna "RowNumber" de nosso conjunto de dados, pois esta coluna parece ser uma numeração automática das linhas.
# Eliminando a coluna RowNumber
data = data.drop(['RowNumber'], axis=1)
# Verificando linhas duplicadas
rows_data = data[data.duplicated()]
if rows_data.empty:
print("Nenhum dado duplicado foi encontrado no conjunto de dados.")
else:
print(f"Foram encontradas {len(rows_data)} entradas duplicadas.")
Nenhum dado duplicado foi encontrado no conjunto de dados.
# Caso houvessem linhas duplicadas, seriam excluidas e impresso o novo conjunto de dados
data = data.drop_duplicates()
data.head()
| CustomerId | Surname | CreditScore | Geography | Gender | Age | Tenure | Balance | NumOfProducts | HasCrCard | IsActiveMember | EstimatedSalary | Exited | Complain | Satisfaction Score | Card Type | Point Earned | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 15634602 | Hargrave | 619 | France | Female | 42 | 2 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 | 1 | 2 | DIAMOND | 464 |
| 1 | 15647311 | Hill | 608 | Spain | Female | 41 | 1 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 | 1 | 3 | DIAMOND | 456 |
| 2 | 15619304 | Onio | 502 | France | Female | 42 | 8 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 | 1 | 3 | DIAMOND | 377 |
| 3 | 15701354 | Boni | 699 | France | Female | 39 | 1 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 | 0 | 5 | GOLD | 350 |
| 4 | 15737888 | Mitchell | 850 | Spain | Female | 43 | 2 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 | 0 | 5 | GOLD | 425 |
Nesta etapa, realizaremos a renomeação dos títulos das colunas para torná-los mais descritivos e facilitar o estudo e a compreensão dos dados.
Isso nos ajudará a trabalhar com os dados de forma mais eficiente e compreensível em etapas posteriores da análise.
# Alterando nome das Colunas
data = data.rename(columns={"CustomerId": "ID",
"Surname": "Sobrenome",
"CreditScore": "PontuacaoCredito",
"Geography": "Localizacao",
"Gender": "Genero",
"Age": "Idade",
"Tenure": "TempoPermanencia",
"Balance": "Saldo",
"NumOfProducts": "Produtos",
"HasCrCard": "TemCartaoCredito",
"IsActiveMember": "MembroAtivo",
"EstimatedSalary": "SalarioEstimado",
"Exited": "Churn",
"Complain": "Reclamacoes",
"Satisfaction Score": "IndiceSatisfacao",
"Card Type": "TipoCartao",
"Point Earned": "PontosAcumulados"})
data.head()
| ID | Sobrenome | PontuacaoCredito | Localizacao | Genero | Idade | TempoPermanencia | Saldo | Produtos | TemCartaoCredito | MembroAtivo | SalarioEstimado | Churn | Reclamacoes | IndiceSatisfacao | TipoCartao | PontosAcumulados | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 15634602 | Hargrave | 619 | France | Female | 42 | 2 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 | 1 | 2 | DIAMOND | 464 |
| 1 | 15647311 | Hill | 608 | Spain | Female | 41 | 1 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 | 1 | 3 | DIAMOND | 456 |
| 2 | 15619304 | Onio | 502 | France | Female | 42 | 8 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 | 1 | 3 | DIAMOND | 377 |
| 3 | 15701354 | Boni | 699 | France | Female | 39 | 1 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 | 0 | 5 | GOLD | 350 |
| 4 | 15737888 | Mitchell | 850 | Spain | Female | 43 | 2 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 | 0 | 5 | GOLD | 425 |
Nesta etapa, procederemos com a exclusão de colunas que são consideradas desnecessárias para nossa análise. A exclusão de colunas que não são relevantes ou não contribuem para nossos objetivos pode simplificar o conjunto de dados e tornar nossa análise mais eficiente.
Após a exclusão, atualizaremos o conjunto de dados resultante para refletir a nova estrutura.
Inicialmente, serão excluídas as colunas ID e Sobrenome, uma vez que são dados de informação interno da instituição não impactando na nossa análise.
# Excluindo colunas desnecessarias (Importante que este processo seja executado depois de exclusão de linhas duplicadas, uma vez que pode ter cliente com perfis semelhantes evitando que esses dados sejam suprimidos)
data = data.drop(['ID',
'Sobrenome'],
axis=1)
data.head()
| PontuacaoCredito | Localizacao | Genero | Idade | TempoPermanencia | Saldo | Produtos | TemCartaoCredito | MembroAtivo | SalarioEstimado | Churn | Reclamacoes | IndiceSatisfacao | TipoCartao | PontosAcumulados | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 619 | France | Female | 42 | 2 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 | 1 | 2 | DIAMOND | 464 |
| 1 | 608 | Spain | Female | 41 | 1 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 | 1 | 3 | DIAMOND | 456 |
| 2 | 502 | France | Female | 42 | 8 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 | 1 | 3 | DIAMOND | 377 |
| 3 | 699 | France | Female | 39 | 1 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 | 0 | 5 | GOLD | 350 |
| 4 | 850 | Spain | Female | 43 | 2 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 | 0 | 5 | GOLD | 425 |
Nesta etapa, exploraremos informações gerais sobre o conjunto de dados para compreender sua estrutura e características. Isso nos ajudará a ter uma visão inicial dos dados com os quais estamos trabalhando.
# Obter informações gerais do conjunto de dados
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 10000 entries, 0 to 9999 Data columns (total 15 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PontuacaoCredito 10000 non-null int64 1 Localizacao 10000 non-null object 2 Genero 10000 non-null object 3 Idade 10000 non-null int64 4 TempoPermanencia 10000 non-null int64 5 Saldo 10000 non-null float64 6 Produtos 10000 non-null int64 7 TemCartaoCredito 10000 non-null int64 8 MembroAtivo 10000 non-null int64 9 SalarioEstimado 10000 non-null float64 10 Churn 10000 non-null int64 11 Reclamacoes 10000 non-null int64 12 IndiceSatisfacao 10000 non-null int64 13 TipoCartao 10000 non-null object 14 PontosAcumulados 10000 non-null int64 dtypes: float64(2), int64(10), object(3) memory usage: 1.2+ MB
É possível destacar que, após os tratamentos iniciais dos dados, o conjunto de dados apresentam variáveis categóricas e numéricas. É importante destacar que esses tratamentos foram preliminares e, à medida do avanço e aprofundamento de nossa análise, novas modificações serão implementadas.
Após a conclusão do primeiro processo de tratamento e transformação do conjunto de dados, avançaremos para a etapa de Análise Exploratória dos Dados (EDA). Durante esta fase, examinaremos cada variável individualmente para identificar seu valor para o estudo. Além disso, faremos uma análise comparativa entre cada variável e a variável alvo "Churn," que é o foco principal de nosso estudo.
Começaremos a análise exploratória examinando cada variável separadamente. Isso inclui:
Iniciaremos a nossa análise exploratória conhecendo os dados das variáveis através do resumo fornecido com informações referente a quantidade de dados, média, desvio-padrão, quartis, valores mínimo e máximo, média e mediana.
# Resumo dos dados do dataset
data.describe()
| PontuacaoCredito | Idade | TempoPermanencia | Saldo | Produtos | TemCartaoCredito | MembroAtivo | SalarioEstimado | Churn | Reclamacoes | IndiceSatisfacao | PontosAcumulados | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.00000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 | 10000.000000 |
| mean | 650.528800 | 38.921800 | 5.012800 | 76485.889288 | 1.530200 | 0.70550 | 0.515100 | 100090.239881 | 0.203800 | 0.204400 | 3.013800 | 606.515100 |
| std | 96.653299 | 10.487806 | 2.892174 | 62397.405202 | 0.581654 | 0.45584 | 0.499797 | 57510.492818 | 0.402842 | 0.403283 | 1.405919 | 225.924839 |
| min | 350.000000 | 18.000000 | 0.000000 | 0.000000 | 1.000000 | 0.00000 | 0.000000 | 11.580000 | 0.000000 | 0.000000 | 1.000000 | 119.000000 |
| 25% | 584.000000 | 32.000000 | 3.000000 | 0.000000 | 1.000000 | 0.00000 | 0.000000 | 51002.110000 | 0.000000 | 0.000000 | 2.000000 | 410.000000 |
| 50% | 652.000000 | 37.000000 | 5.000000 | 97198.540000 | 1.000000 | 1.00000 | 1.000000 | 100193.915000 | 0.000000 | 0.000000 | 3.000000 | 605.000000 |
| 75% | 718.000000 | 44.000000 | 7.000000 | 127644.240000 | 2.000000 | 1.00000 | 1.000000 | 149388.247500 | 0.000000 | 0.000000 | 4.000000 | 801.000000 |
| max | 850.000000 | 92.000000 | 10.000000 | 250898.090000 | 4.000000 | 1.00000 | 1.000000 | 199992.480000 | 1.000000 | 1.000000 | 5.000000 | 1000.000000 |
Essas informações iniciais proporcionam uma visão geral do comportamento dos dados em relação às variáveis. É possível notar a presença de outliers em variáveis, como por exemplo idade e a existência de variáveis binárias, como TemCartaoCredito, Churn e Reclamacoes. É importante realizar essas observações como ponto de partida para análises mais detalhadas e para entender melhor os padrões e tendências no seu conjunto de dados.
A variável CHURN demonstra os clientes que permaneceram ou deixaram a empresa até a data da coleta de dados.
Assim sendo, vamos quantificar e visualizar esses dados.
# Contando a quantidade de clientes
clientes_churn = data['Churn'].value_counts()
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
clientes_churn.index = clientes_churn.index.map({0: 'Não', 1: 'Sim'})
# Criando um DataFrame a partir dos valores contados
tb_churn = pd.DataFrame({'Churn': clientes_churn.index,
'Clientes': clientes_churn.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_churn['Clientes'].sum()
linha_total = pd.DataFrame({'Churn': ['Total'],
'Clientes': [total_clientes]})
tb_churn = pd.concat([tb_churn, linha_total], ignore_index=True)
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14, 6))
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_churn.values,
colLabels=tb_churn.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white'],
['lightgray', 'lightgray']],
colLoc='center',
colWidths=[0.15, 0.15])
axes[0].set_title('Tabela de Churn', fontsize=14)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=clientes_churn.index, y=clientes_churn.values, palette=['darkblue', 'tomato'], ax=axes[1])
axes[1].set_xlabel('Churn')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição dos Clientes', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(clientes_churn.values):
axes[1].text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
Na análise da nossa primeira variável, os resultados revelam que, dos 10.000 clientes analisados, 20,38% encerraram a sua relação com a empresa, enquanto a grande maioria, 79,62% dos clientes, permanecem como clientes da instituição. A quantificação desta variável tem por objetivo fornecer uma visão clara da distribuição dos clientes em relação ao churn. No discorrer dos próximos tópicos, sempre será abordado o comportamento desta variável sobre as demais, afim de proporcionar causa e efeito de cada uma sobre uma possível tomada de decisão do cliente para sair da instituição.
Na variável PONTUAÇÃO DE CRÉDITO vamos explorar as distribuição destes dados e como elas estão relacionadas com o Churn.
# Resumo dos dados da variável Pontuação de Crédito
data['PontuacaoCredito'].describe()
count 10000.000000 mean 650.528800 std 96.653299 min 350.000000 25% 584.000000 50% 652.000000 75% 718.000000 max 850.000000 Name: PontuacaoCredito, dtype: float64
Usaremos um 'histograma' para visualizar a distribuição das pontuações de crédito. Isso nos permitirá entender como as pontuações estão dispersas e identificar padrões na distribuição.
# Histograma para visualizar a distribuição dos dados
plt.figure(figsize=(16, 6))
sns.histplot(data['PontuacaoCredito'], bins=30, kde=True)
plt.title('Distribuição da Pontuação de Crédito')
plt.xlabel('Pontuação de Crédito')
plt.ylabel('Frequência')
plt.show()
A análise da pontuação de crédito mostra que os valores variam de 350 a 850, com uma média de aproximadamente 650,52. A distribuição das pontuações de crédito apresenta uma simetria, com a maioria das pontuações agrupadas em torno da média. Há uma pequena queda nas pontuações mais baixas e mais altas, indicando que há menos clientes com pontuações extremamente baixas ou altas.
Será definido uma divisão em grupos para melhor formatação e visualização dos dados. Com essa nova formatação, é possível identificar melhor, visualmente, a relação com a variável churn e, consequentemente, auxiliar na compreensão do comportamento das duas variáveis.
# Definir os rótulos dos grupos
labels = ["{0} - {1}".format(i, i + 49) for i in range(301, 850, 50)]
# Criar uma nova coluna com base nos intervalos
data['PontuacaoCredito_grupo'] = pd.cut(data.PontuacaoCredito,
bins=range(301, 901, 50),
right=False,
labels=labels)
# Verificando os dados da nova coluna
data['PontuacaoCredito_grupo'].value_counts()
651 - 700 1947 601 - 650 1871 701 - 750 1518 551 - 600 1445 501 - 550 978 751 - 800 953 801 - 850 645 451 - 500 454 401 - 450 170 351 - 400 14 301 - 350 5 Name: PontuacaoCredito_grupo, dtype: int64
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_PontCredito = data.groupby('PontuacaoCredito_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_PontCredito['Total'] = grouped_PontCredito[0] + grouped_PontCredito[1]
grouped_PontCredito['Não_Churn_Percent'] = (grouped_PontCredito[0] / grouped_PontCredito['Total']) * 100
grouped_PontCredito['Churn_Percent'] = (grouped_PontCredito[1] / grouped_PontCredito['Total']) * 100
# Renomear as colunas
grouped_PontCredito.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_PontCredito.reset_index(inplace=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(grouped_PontCredito, headers='keys', tablefmt='pretty'))
+----+------------------------+-----------+-------+-------+-------------------+--------------------+ | | PontuacaoCredito_grupo | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +----+------------------------+-----------+-------+-------+-------------------+--------------------+ | 0 | 301 - 350 | 0 | 5 | 5 | 0.0 | 100.0 | | 1 | 351 - 400 | 0 | 14 | 14 | 0.0 | 100.0 | | 2 | 401 - 450 | 128 | 42 | 170 | 75.29411764705883 | 24.705882352941178 | | 3 | 451 - 500 | 363 | 91 | 454 | 79.95594713656388 | 20.044052863436125 | | 4 | 501 - 550 | 762 | 216 | 978 | 77.91411042944786 | 22.085889570552148 | | 5 | 551 - 600 | 1148 | 297 | 1445 | 79.44636678200692 | 20.55363321799308 | | 6 | 601 - 650 | 1479 | 392 | 1871 | 79.04863709246392 | 20.95136290753608 | | 7 | 651 - 700 | 1586 | 361 | 1947 | 81.45865434001027 | 18.541345659989727 | | 8 | 701 - 750 | 1212 | 306 | 1518 | 79.84189723320159 | 20.158102766798418 | | 9 | 751 - 800 | 766 | 187 | 953 | 80.37775445960126 | 19.62224554039874 | | 10 | 801 - 850 | 518 | 127 | 645 | 80.31007751937985 | 19.689922480620154 | +----+------------------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados por PontuacaoCredito_grupo e Churn e contar as ocorrências
grouped_PontCredito = data.groupby(['PontuacaoCredito_grupo', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_PontCredito.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (12, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_PontCredito[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Pontuação de Crédito')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Pontuação de Crédito')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
# Box plot da Pontuação de Crédito para clientes Churn e não-Churn
sns.set(style="whitegrid")
# Definindo os limites para equalizações dos boxplot
lim_min_pontcredito = min(data['PontuacaoCredito'])
lim_max_pontcredito = max(data['PontuacaoCredito'])
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(16, 5))
# Box plot 1 (Clientes Churn)
sns.boxplot(ax=axes[0], x='PontuacaoCredito', data=data[data['Churn'] == 1])
axes[0].set_title('Clientes Churn')
# Definir limites iguais para os eixos x
axes[0].set_xlim(lim_min_pontcredito, lim_max_pontcredito)
# Box plot 2 (Clientes Não Churn)
sns.boxplot(ax=axes[1], x='PontuacaoCredito', data=data[data['Churn'] == 0])
axes[1].set_title('Clientes Não Churn')
# Definir limites iguais para os eixos x
axes[1].set_xlim(lim_min_pontcredito, lim_max_pontcredito)
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
Na distribuição de dados exibida é possível observar que existe uma proporcionalidade entre os grupos de pontuação de crédito criados na relação Churn x Não Churn. Todavia, destaca-se que as pontuações baixas foram identificadas, visualmente com auxilio do boxplot, que essas camadas sãoo clientes exclusivamente que não são mais clientes. Identificação importante que podemos estar excluíndo de nosso modelo futuramente.
A exploração da variável IDADE será semelhante a realizada na Pontuação de Crédito.
# Resumo dos dados da variável Idade
data['Idade'].describe()
count 10000.000000 mean 38.921800 std 10.487806 min 18.000000 25% 32.000000 50% 37.000000 75% 44.000000 max 92.000000 Name: Idade, dtype: float64
# Histograma
plt.figure(figsize=(16, 6))
sns.histplot(data['Idade'], bins=30, kde=True)
plt.title('Distribuição da Idade')
plt.xlabel('Idade')
plt.ylabel('Frequência')
plt.show()
A variável Idade possui dados que variam de 18 a 92 anos, com uma média de aproximadamente 39 anos. A maioria dos clientes tem idades entre 32 e 44 anos, conforme indicado pelos percentis.
Pelo histograma, identifica-se que a distribuição apresenta simetria é à direita, com uma concentração maior de clientes com idade entre 30 à 50 anos, e uma cauda longa à direita, impactada pelos clientes com idade mais avançada.
# Definir os rótulos dos grupos
labels = ["{0} - {1}".format(i, i + 9)
for i in range(11, 101, 10)]
# Criar uma nova coluna
data['Idade_grupo'] = pd.cut(data.Idade,
bins=range(11, 102, 10),
right=False,
labels=labels)
# Verificando os dados da nova coluna
data['Idade_grupo'].value_counts()
31 - 40 4451 41 - 50 2320 21 - 30 1879 51 - 60 797 61 - 70 331 71 - 80 121 11 - 20 89 81 - 90 10 91 - 100 2 Name: Idade_grupo, dtype: int64
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_Idade = data.groupby('Idade_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_Idade['Total'] = grouped_Idade[0] + grouped_Idade[1]
grouped_Idade['Não_Churn_Percent'] = (grouped_Idade[0] / grouped_Idade['Total']) * 100
grouped_Idade['Churn_Percent'] = (grouped_Idade[1] / grouped_Idade['Total']) * 100
# Renomear as colunas
grouped_Idade.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_Idade.reset_index(inplace=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(grouped_Idade, headers='keys', tablefmt='pretty'))
+---+-------------+-----------+-------+-------+-------------------+--------------------+ | | Idade_grupo | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+-------------+-----------+-------+-------+-------------------+--------------------+ | 0 | 11 - 20 | 84 | 5 | 89 | 94.3820224719101 | 5.617977528089887 | | 1 | 21 - 30 | 1736 | 143 | 1879 | 92.38956891963811 | 7.610431080361894 | | 2 | 31 - 40 | 3912 | 539 | 4451 | 87.8903617164682 | 12.10963828353179 | | 3 | 41 - 50 | 1532 | 788 | 2320 | 66.0344827586207 | 33.96551724137931 | | 4 | 51 - 60 | 349 | 448 | 797 | 43.7892095357591 | 56.2107904642409 | | 5 | 61 - 70 | 227 | 104 | 331 | 68.58006042296071 | 31.419939577039273 | | 6 | 71 - 80 | 111 | 10 | 121 | 91.73553719008265 | 8.264462809917356 | | 7 | 81 - 90 | 9 | 1 | 10 | 90.0 | 10.0 | | 8 | 91 - 100 | 2 | 0 | 2 | 100.0 | 0.0 | +---+-------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências
grouped_Idade = data.groupby(['Idade_grupo', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_Idade.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (12, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_Idade[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Idade')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Idade')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
# Box plot da Pontuação de Crédito para clientes Churn e não-Churn
sns.set(style="whitegrid")
# Definindo os limites para equalizações dos boxplot
lim_min_idade = math.floor(min(data['Idade'])/ 10) * 10
lim_max_idade = math.ceil(max(data['Idade']) / 10) * 10
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(16, 5))
# Box plot 1 (Clientes Churn)
sns.boxplot(ax=axes[0], x='Idade', data=data[data['Churn'] == 1])
axes[0].set_title('Clientes Churn')
# Definir limites iguais para os eixos x
axes[0].set_xlim(lim_min_idade, lim_max_idade)
# Box plot 2 (Clientes Não Churn)
sns.boxplot(ax=axes[1], x='Idade', data=data[data['Churn'] == 0])
axes[1].set_title('Clientes Não Churn')
# Definir limites iguais para os eixos x
axes[1].set_xlim(lim_min_idade, lim_max_idade)
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
Nesta variável, duas observações são importantes e interessantes para tratamentos e modelos futuros. As idades mais avançadas podem ser suprimidas do modelo por possuir poucas observações interferindo no modelo.
Na relação Churn x Não Churn ocorre bastante desproporcionalidade entre os intervalos, podendo ser necessário fazer um balanceamento dos dados para a criação de um modelo preditivo.
Na variável TEMPO DE PERMANÊNCIA o intervalo dos dados é menor, não sendo necessário fazer uma classificação por grupo.
# Resumo dos dados
data['TempoPermanencia'].describe()
count 10000.000000 mean 5.012800 std 2.892174 min 0.000000 25% 3.000000 50% 5.000000 75% 7.000000 max 10.000000 Name: TempoPermanencia, dtype: float64
# Contando a quantidade de clientes
clientes_permanencia = data['TempoPermanencia'].value_counts()
# Criando um DataFrame a partir dos valores contados
tb_permanencia = pd.DataFrame({'Tempo Permanência (Anos)': clientes_permanencia.index,
'Clientes': clientes_permanencia.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_permanencia['Clientes'].sum()
linha_total = pd.DataFrame({'Tempo Permanência (Anos)': ['Total'],
'Clientes': [total_clientes]})
tb_permanencia = pd.concat([tb_permanencia, linha_total], ignore_index=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(tb_permanencia, headers='keys', tablefmt='pretty'))
+----+--------------------------+----------+ | | Tempo Permanência (Anos) | Clientes | +----+--------------------------+----------+ | 0 | 2 | 1048 | | 1 | 1 | 1035 | | 2 | 7 | 1028 | | 3 | 8 | 1025 | | 4 | 5 | 1012 | | 5 | 3 | 1009 | | 6 | 4 | 989 | | 7 | 9 | 984 | | 8 | 6 | 967 | | 9 | 10 | 490 | | 10 | 0 | 413 | | 11 | Total | 10000 | +----+--------------------------+----------+
# Contagem de frequência
frequencia_permanencia = data['TempoPermanencia'].value_counts()
# Ordenando a série
frequencia_permanencia = frequencia_permanencia.sort_index()
# Gráfico de barras
plt.figure(figsize=(16, 6))
sns.barplot(x=frequencia_permanencia.index,
y=frequencia_permanencia.values,
color='#657BA6')
plt.xlabel('Tempo de Permanência (Anos)')
plt.ylabel('Frequência')
plt.title('Distribuição do Tempo de Permanência')
# Adicionando os rótulos de dados com alinhamento correto
for index, value in enumerate(frequencia_permanencia.values):
plt.text(index, value, str(value), ha='center', va='bottom')
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_permanencia = data.groupby('TempoPermanencia')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_permanencia['Total'] = grouped_permanencia[0] + grouped_permanencia[1]
grouped_permanencia['Não_Churn_Percent'] = (grouped_permanencia[0] / grouped_permanencia['Total']) * 100
grouped_permanencia['Churn_Percent'] = (grouped_permanencia[1] / grouped_permanencia['Total']) * 100
# Renomear as colunas
grouped_permanencia.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
# Resetar o índice para ter 'PontuacaoCredito_grupo' como uma coluna
grouped_permanencia.reset_index(inplace=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(grouped_permanencia, headers='keys', tablefmt='pretty'))
+----+------------------+-----------+-------+--------+-------------------+--------------------+ | | TempoPermanencia | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +----+------------------+-----------+-------+--------+-------------------+--------------------+ | 0 | 0.0 | 318.0 | 95.0 | 413.0 | 76.99757869249395 | 23.002421307506054 | | 1 | 1.0 | 803.0 | 232.0 | 1035.0 | 77.58454106280193 | 22.415458937198068 | | 2 | 2.0 | 847.0 | 201.0 | 1048.0 | 80.82061068702289 | 19.1793893129771 | | 3 | 3.0 | 796.0 | 213.0 | 1009.0 | 78.88999008919723 | 21.110009910802773 | | 4 | 4.0 | 786.0 | 203.0 | 989.0 | 79.474216380182 | 20.525783619817997 | | 5 | 5.0 | 803.0 | 209.0 | 1012.0 | 79.34782608695652 | 20.652173913043477 | | 6 | 6.0 | 771.0 | 196.0 | 967.0 | 79.73112719751809 | 20.2688728024819 | | 7 | 7.0 | 851.0 | 177.0 | 1028.0 | 82.78210116731518 | 17.217898832684824 | | 8 | 8.0 | 828.0 | 197.0 | 1025.0 | 80.78048780487805 | 19.21951219512195 | | 9 | 9.0 | 770.0 | 214.0 | 984.0 | 78.2520325203252 | 21.747967479674795 | | 10 | 10.0 | 389.0 | 101.0 | 490.0 | 79.38775510204081 | 20.612244897959183 | +----+------------------+-----------+-------+--------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências
grouped_permanencia = data.groupby(['TempoPermanencia', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_permanencia.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (12, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_permanencia[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Tempo de Permanência')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Tempo de Permanência (em anos)')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
As informações do Tempo de Permanência dos clientes na instituição demonstram valores totalmente balanceados, cujo valores são proporcionais em todos os intervalos de tempo. Desta forma, esta variável, no momento, não possui uma característica que possa influenciar a decisão do cliente de deixar a companhia, indicando que não há padrões discerníveis que expliquem o Churn do cliente.
Na variável SALDO EM CONTA o intervalo de dados é maior, e, assim, será agrupado os dados para melhor visualização
# Resumo dos dados da variável Saldo em Conta
data['Saldo'].describe()
count 10000.000000 mean 76485.889288 std 62397.405202 min 0.000000 25% 0.000000 50% 97198.540000 75% 127644.240000 max 250898.090000 Name: Saldo, dtype: float64
# Histograma
plt.figure(figsize=(16, 6))
sns.histplot(data['Saldo'], bins=30, kde=True)
plt.title('Distribuição do Saldo em Conta')
plt.xlabel('Saldo em Conta')
plt.ylabel('Frequência')
plt.show()
Com base na distribuição da variável Saldo em Conta, podemos inferir que a distribuição é assimétrica, evidenciado pelo fato de que o número de clientes em cada intervalo de saldo não é uniforme. A maioria dos clientes parece ter saldos menores, enquanto há relativamente poucos clientes com saldos mais altos.
# Definir os rótulos dos grupos
labels = ["{0} - {1}".format(i, i + 24999)
for i in range(0, 275000, 25000)]
# Criar uma nova coluna "Saldo_grupo" com base nos intervalos
data['Saldo_grupo'] = pd.cut(data.Saldo,
bins=range(0, 275001, 25000),
right=False,
labels=labels)
# Verificando os dados da nova coluna
data['Saldo_grupo'].value_counts()
0 - 24999 3623 100000 - 124999 2068 125000 - 149999 1762 75000 - 99999 1160 150000 - 174999 738 50000 - 74999 349 175000 - 199999 197 25000 - 49999 69 200000 - 224999 32 225000 - 249999 1 250000 - 274999 1 Name: Saldo_grupo, dtype: int64
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_saldo = data.groupby('Saldo_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_saldo['Total'] = grouped_saldo[0] + grouped_saldo[1]
grouped_saldo['Não_Churn_Percent'] = (grouped_saldo[0] / grouped_saldo['Total']) * 100
grouped_saldo['Churn_Percent'] = (grouped_saldo[1] / grouped_saldo['Total']) * 100
# Renomear as colunas
grouped_saldo.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
# Resetar o índice para ter 'PontuacaoCredito_grupo' como uma coluna
grouped_saldo.reset_index(inplace=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(grouped_saldo, headers='keys', tablefmt='pretty'))
+----+-----------------+-----------+-------+-------+-------------------+--------------------+ | | Saldo_grupo | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +----+-----------------+-----------+-------+-------+-------------------+--------------------+ | 0 | 0 - 24999 | 3119 | 504 | 3623 | 86.08887662158432 | 13.911123378415677 | | 1 | 25000 - 49999 | 47 | 22 | 69 | 68.11594202898551 | 31.88405797101449 | | 2 | 50000 - 74999 | 274 | 75 | 349 | 78.51002865329512 | 21.48997134670487 | | 3 | 75000 - 99999 | 934 | 226 | 1160 | 80.51724137931035 | 19.482758620689655 | | 4 | 100000 - 124999 | 1510 | 558 | 2068 | 73.0174081237911 | 26.9825918762089 | | 5 | 125000 - 149999 | 1333 | 429 | 1762 | 75.65266742338252 | 24.347332576617482 | | 6 | 150000 - 174999 | 582 | 156 | 738 | 78.86178861788618 | 21.138211382113823 | | 7 | 175000 - 199999 | 148 | 49 | 197 | 75.1269035532995 | 24.873096446700508 | | 8 | 200000 - 224999 | 15 | 17 | 32 | 46.875 | 53.125 | | 9 | 225000 - 249999 | 0 | 1 | 1 | 0.0 | 100.0 | | 10 | 250000 - 274999 | 0 | 1 | 1 | 0.0 | 100.0 | +----+-----------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências
grouped_saldo = data.groupby(['Saldo_grupo', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_saldo.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (16, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_saldo[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Saldo em Conta')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Saldo em Conta')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=50)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
# Box plot da Pontuação de Crédito para clientes Churn e não-Churn
sns.set(style="whitegrid")
# Definindo os limites para equalizações dos boxplot
lim_min_pontcredito = math.floor(min(data['Idade'])/ 10) * 10
lim_max_pontcredito = math.ceil(max(data['Idade']) / 10) * 10
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(16, 5))
# Box plot 1 (Clientes Churn)
sns.boxplot(ax=axes[0], x='Idade', data=data[data['Churn'] == 1])
axes[0].set_title('Clientes Churn')
# Definir limites iguais para os eixos x
axes[0].set_xlim(lim_min_pontcredito, lim_max_pontcredito)
# Box plot 2 (Clientes Não Churn)
sns.boxplot(ax=axes[1], x='Idade', data=data[data['Churn'] == 0])
axes[1].set_title('Clientes Não Churn')
# Definir limites iguais para os eixos x
axes[1].set_xlim(lim_min_pontcredito, lim_max_pontcredito)
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
Além da assimetria identificada, observa-se a existência de muito outliers nesta variável, sendo necessário encontrar uma forma de balanceamento destes dados, objetivando a remoção de dados que não possuem relevância e preservação da integridade e precisão do nosso modelo.
A variável PRODUTOS apresenta a quantidade de serviços que os clientes contrataram.
# Resumo dos dados
data['Produtos'].describe()
count 10000.000000 mean 1.530200 std 0.581654 min 1.000000 25% 1.000000 50% 1.000000 75% 2.000000 max 4.000000 Name: Produtos, dtype: float64
# Contando a quantidade de clientes
clientes_Produtos= data['Produtos'].value_counts()
# Criando um DataFrame a partir dos valores contados
tb_Produtos = pd.DataFrame({'Produtos': clientes_Produtos.index,
'Clientes': clientes_Produtos.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_Produtos['Clientes'].sum()
linha_total = pd.DataFrame({'Produtos': ['Total'],
'Clientes': [total_clientes]})
tb_Produtos = pd.concat([tb_Produtos, linha_total], ignore_index=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(tb_Produtos, headers='keys', tablefmt='pretty'))
+---+----------+----------+ | | Produtos | Clientes | +---+----------+----------+ | 0 | 1 | 5084 | | 1 | 2 | 4590 | | 2 | 3 | 266 | | 3 | 4 | 60 | | 4 | Total | 10000 | +---+----------+----------+
# Contagem de frequência
frequencia_produtos = data['Produtos'].value_counts()
# Ordenando a série
frequencia_produtos = frequencia_produtos.sort_index()
# Gráfico de barras
plt.figure(figsize=(16, 6))
sns.barplot(x=frequencia_produtos.index,
y=frequencia_produtos.values,
color='#657BA6')
plt.xlabel('Produtos')
plt.ylabel('Frequência')
plt.title('Distribuição da Quantidade de Produtos')
# Adicionando os rótulos de dados com alinhamento correto
for index, value in enumerate(frequencia_produtos.values):
plt.text(index, value, str(value), ha='center', va='bottom')
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_produtos = data.groupby('Produtos')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_produtos['Total'] = grouped_produtos[0] + grouped_produtos[1]
grouped_produtos['Não_Churn_Percent'] = (grouped_produtos[0] / grouped_produtos['Total']) * 100
grouped_produtos['Churn_Percent'] = (grouped_produtos[1] / grouped_produtos['Total']) * 100
# Renomear as colunas
grouped_produtos.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
# Resetar o índice para ter 'PontuacaoCredito_grupo' como uma coluna
grouped_produtos.reset_index(inplace=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(grouped_produtos, headers='keys', tablefmt='pretty'))
+---+----------+-----------+--------+--------+--------------------+--------------------+ | | Produtos | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+----------+-----------+--------+--------+--------------------+--------------------+ | 0 | 1.0 | 3675.0 | 1409.0 | 5084.0 | 72.28560188827696 | 27.714398111723053 | | 1 | 2.0 | 4241.0 | 349.0 | 4590.0 | 92.39651416122004 | 7.603485838779957 | | 2 | 3.0 | 46.0 | 220.0 | 266.0 | 17.293233082706767 | 82.70676691729322 | | 3 | 4.0 | 0.0 | 60.0 | 60.0 | 0.0 | 100.0 | +---+----------+-----------+--------+--------+--------------------+--------------------+
# Agrupar os dados e contar as ocorrências
grouped_produtos = data.groupby(['Produtos', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_produtos.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (12, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_produtos[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Produtos')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Quantidade de Produtos')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
A análise dos produtos em relação ao Churn demonstra que há uma forte tendência entre o número de produtos e a probabilidade de o cliente sair da empresa. Possivelmente, a identificação do comportamento de cada serviço para as demais variáveis tornaria o nosso modelo mais eficiente, e permitiria a companhia a avaliação dos serviços que estão sendo ofertados.
A variável SALÁRIO temos uma estimativa do salário dos clientes.
# Resumo dos dados da variável Salario Estimado
data['SalarioEstimado'].describe()
count 10000.000000 mean 100090.239881 std 57510.492818 min 11.580000 25% 51002.110000 50% 100193.915000 75% 149388.247500 max 199992.480000 Name: SalarioEstimado, dtype: float64
# Histograma
plt.figure(figsize=(16, 6))
sns.histplot(data['SalarioEstimado'], bins=30, kde=True)
plt.title('Distribuição do Salário')
plt.xlabel('Salário Estimado')
plt.ylabel('Frequência')
plt.show()
A distribuição dos dados da variável Salário apresenta uma simetria, em que os valores apresentam números semelhantes entre as observações.
# Definir os rótulos dos grupos
labels = ["{0} - {1}".format(i, i + 19999) for i in range(0, 200000, 20000)]
# Criar uma nova coluna "Salario_grupo" com base nos intervalos
data['Salario_grupo'] = pd.cut(data.SalarioEstimado,
bins=range(0, 200001, 20000),
right=False,
labels=labels)
# Verificando os dados da nova coluna
data['Salario_grupo'].value_counts()
60000 - 79999 1027 100000 - 119999 1027 160000 - 179999 1009 120000 - 139999 1007 40000 - 59999 1006 80000 - 99999 1002 0 - 19999 986 180000 - 199999 985 140000 - 159999 982 20000 - 39999 969 Name: Salario_grupo, dtype: int64
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_Salario = data.groupby('Salario_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_Salario['Total'] = grouped_Salario[0] + grouped_Salario[1]
grouped_Salario['Não_Churn_Percent'] = (grouped_Salario[0] / grouped_Salario['Total']) * 100
grouped_Salario['Churn_Percent'] = (grouped_Salario[1] / grouped_Salario['Total']) * 100
# Renomear as colunas
grouped_Salario.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_Salario.reset_index(inplace=True)
# Imprimir a tabela a partir do DataFrame
print(tabulate(grouped_Salario, headers='keys', tablefmt='pretty'))
+---+-----------------+-----------+-------+-------+-------------------+--------------------+ | | Salario_grupo | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+-----------------+-----------+-------+-------+-------------------+--------------------+ | 0 | 0 - 19999 | 788 | 198 | 986 | 79.91886409736308 | 20.08113590263692 | | 1 | 20000 - 39999 | 773 | 196 | 969 | 79.77296181630547 | 20.227038183694532 | | 2 | 40000 - 59999 | 815 | 191 | 1006 | 81.01391650099403 | 18.986083499005964 | | 3 | 60000 - 79999 | 815 | 212 | 1027 | 79.35735150925024 | 20.642648490749757 | | 4 | 80000 - 99999 | 806 | 196 | 1002 | 80.43912175648703 | 19.560878243512974 | | 5 | 100000 - 119999 | 816 | 211 | 1027 | 79.45472249269717 | 20.545277507302824 | | 6 | 120000 - 139999 | 811 | 196 | 1007 | 80.53624627606753 | 19.463753723932474 | | 7 | 140000 - 159999 | 776 | 206 | 982 | 79.0224032586558 | 20.977596741344197 | | 8 | 160000 - 179999 | 784 | 225 | 1009 | 77.70069375619426 | 22.29930624380575 | | 9 | 180000 - 199999 | 778 | 207 | 985 | 78.98477157360406 | 21.01522842639594 | +---+-----------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_Salario = data.groupby('Salario_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_Salario.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (16, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_Salario[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Salário Estimado')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Salário')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=25)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
# Box plot para clientes Churn e não-Churn
sns.set(style="whitegrid")
# Definindo os limites para equalizações dos boxplot
lim_min_salario = -1000
lim_max_salario = math.ceil(max(data['SalarioEstimado']) / 10000) * 10000
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(16, 5))
# Box plot 1 (Clientes Churn)
sns.boxplot(ax=axes[0], x='SalarioEstimado', data=data[data['Churn'] == 1])
axes[0].set_title('Clientes Churn')
# Definir limites iguais para os eixos x
axes[0].set_xlim(lim_min_salario, lim_max_salario)
# Box plot 2 (Clientes Não Churn)
sns.boxplot(ax=axes[1], x='SalarioEstimado', data=data[data['Churn'] == 0])
axes[1].set_title('Clientes Não Churn')
# Definir limites iguais para os eixos x
axes[1].set_xlim(lim_min_salario, lim_max_salario)
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
A relação entre Churn e Não Churn para esta variável também revela proporções idênticas entre as faixas salariais atribuídas, indicando um equilíbrio homogêneo nos dados. Dessa forma, a distribuição uniforme das observações nas diferentes faixas salariais sugere uma representação equitativa das categorias, facilitando uma análise mais precisa das relações entre o salário e a decisão dos clientes em relação à rotatividade.
O ÍNDICE DE SATISFAÇÃO é feito através de uma avaliação do cliente de 1 a 5, medindo a quealidade dos serviços prestados da companhia.
# Resumo dos dados da variável Indice de Satisfação
data['IndiceSatisfacao'].describe()
count 10000.000000 mean 3.013800 std 1.405919 min 1.000000 25% 2.000000 50% 3.000000 75% 4.000000 max 5.000000 Name: IndiceSatisfacao, dtype: float64
# Contando a quantidade de clientes
clientes_satisafacao = data['IndiceSatisfacao'].value_counts()
# Criando um DataFrame a partir dos valores contados
tb_satisfacao = pd.DataFrame({'Satifação': clientes_satisafacao.index,
'Clientes': clientes_satisafacao.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_satisfacao['Clientes'].sum()
linha_total = pd.DataFrame({'Satifação': ['Total'],
'Clientes': [total_clientes]})
tb_satisfacao = pd.concat([tb_satisfacao, linha_total], ignore_index=True)
# Verificando os dados da nova coluna
data['IndiceSatisfacao'].value_counts()
3 2042 2 2014 4 2008 5 2004 1 1932 Name: IndiceSatisfacao, dtype: int64
# Contagem de frequência
frequencia_satisfacao = data['IndiceSatisfacao'].value_counts()
# Ordenando a série
frequencia_satisfacao = frequencia_satisfacao.sort_index()
# Gráfico de barras
plt.figure(figsize=(16, 6))
sns.barplot(x=frequencia_satisfacao.index,
y=frequencia_satisfacao.values,
color='#657BA6')
plt.xlabel('Índice de Satisfação')
plt.ylabel('Frequência')
plt.title('Distribuição do Índice de Satisfação do Cliente')
# Adicionando os rótulos de dados com alinhamento correto
for index, value in enumerate(frequencia_satisfacao.values):
plt.text(index, value, str(value), ha='center', va='bottom')
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_satisfacao = data.groupby('IndiceSatisfacao')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_satisfacao['Total'] = grouped_satisfacao[0] + grouped_satisfacao[1]
grouped_satisfacao['Não_Churn_Percent'] = (grouped_satisfacao[0] / grouped_satisfacao['Total']) * 100
grouped_satisfacao['Churn_Percent'] = (grouped_satisfacao[1] / grouped_satisfacao['Total']) * 100
# Renomear as colunas
grouped_satisfacao.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_satisfacao.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_satisfacao, headers='keys', tablefmt='pretty'))
+---+------------------+-----------+-------+--------+-------------------+--------------------+ | | IndiceSatisfacao | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+------------------+-----------+-------+--------+-------------------+--------------------+ | 0 | 1.0 | 1545.0 | 387.0 | 1932.0 | 79.96894409937887 | 20.031055900621116 | | 1 | 2.0 | 1575.0 | 439.0 | 2014.0 | 78.2025819265144 | 21.7974180734856 | | 2 | 3.0 | 1641.0 | 401.0 | 2042.0 | 80.36238981390792 | 19.637610186092065 | | 3 | 4.0 | 1594.0 | 414.0 | 2008.0 | 79.38247011952191 | 20.617529880478088 | | 4 | 5.0 | 1607.0 | 397.0 | 2004.0 | 80.18962075848304 | 19.810379241516966 | +---+------------------+-----------+-------+--------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_satisfacao = data.groupby('IndiceSatisfacao')['Churn'].value_counts().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_satisfacao.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (16, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_satisfacao[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Índice de Satisfação do Cliente')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Satisfação do Cliente')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
Nesta variável infere-se que a distribuição dos dados é homogênea, tanto quando avaliado a variável individualmente, quanto quando avaliado pelo Churn. Diante destas informações, inicialmente, acredista-se como um fator não relevante para o cliente deixar a empresa, uma vez que não é possível identificar alguma característica que possa ser predominante nesta variável.
# Resumo dos dados da variável PontosAcumulados
data['PontosAcumulados'].describe()
count 10000.000000 mean 606.515100 std 225.924839 min 119.000000 25% 410.000000 50% 605.000000 75% 801.000000 max 1000.000000 Name: PontosAcumulados, dtype: float64
# Histograma
plt.figure(figsize=(16, 6))
sns.histplot(data['PontosAcumulados'], bins=30, kde=True)
plt.title('Distribuição da Pontuação Acumulada')
plt.xlabel('Pontuação Acumulada')
plt.ylabel('Frequência')
plt.show()
Os dados da variável Pontos Acumulados exibem uma distribuição que varia de 119 a 1000, com uma média de aproximadamente 606.52. A distribuição dos dados sugere que poucos clientes estão nas faixas menores de pontos acumulados, ocorrendo uma distribuição homogênea entre os demais intervalos.
# Definir os rótulos dos grupos
labels = ["{0} - {1}".format(i, i + 99) for i in range(101, 1000, 100)]
# Criar uma nova coluna
data['PontosAcumulados_grupo'] = pd.cut(data.PontosAcumulados,
bins=range(101, 1002, 100),
right=False,
labels=labels)
# Verificando os dados da nova coluna
data['PontosAcumulados_grupo'].value_counts()
501 - 600 1350 701 - 800 1279 601 - 700 1274 301 - 400 1267 901 - 1000 1258 801 - 900 1243 401 - 500 1239 201 - 300 1088 101 - 200 2 Name: PontosAcumulados_grupo, dtype: int64
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_PontAcum = data.groupby('PontosAcumulados_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_PontAcum['Total'] = grouped_PontAcum[0] + grouped_PontAcum[1]
grouped_PontAcum['Não_Churn_Percent'] = (grouped_PontAcum[0] / grouped_PontAcum['Total']) * 100
grouped_PontAcum['Churn_Percent'] = (grouped_PontAcum[1] / grouped_PontAcum['Total']) * 100
# Renomear as colunas
grouped_PontAcum.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_PontAcum.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_PontAcum, headers='keys', tablefmt='pretty'))
+---+------------------------+-----------+-------+-------+-------------------+--------------------+ | | PontosAcumulados_grupo | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+------------------------+-----------+-------+-------+-------------------+--------------------+ | 0 | 101 - 200 | 1 | 1 | 2 | 50.0 | 50.0 | | 1 | 201 - 300 | 853 | 235 | 1088 | 78.40073529411765 | 21.599264705882355 | | 2 | 301 - 400 | 1007 | 260 | 1267 | 79.47908445146015 | 20.520915548539858 | | 3 | 401 - 500 | 1004 | 235 | 1239 | 81.03309120258272 | 18.966908797417272 | | 4 | 501 - 600 | 1083 | 267 | 1350 | 80.22222222222221 | 19.77777777777778 | | 5 | 601 - 700 | 1009 | 265 | 1274 | 79.19937205651492 | 20.800627943485086 | | 6 | 701 - 800 | 999 | 280 | 1279 | 78.1078967943706 | 21.892103205629397 | | 7 | 801 - 900 | 989 | 254 | 1243 | 79.56556717618665 | 20.434432823813356 | | 8 | 901 - 1000 | 1017 | 241 | 1258 | 80.84260731319554 | 19.15739268680445 | +---+------------------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_PontAcum = data.groupby('PontosAcumulados_grupo')['Churn'].value_counts().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_PontAcum.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (16, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_PontAcum[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Pontuação Acumulada')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Pontuação Acumulada')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
# Box plot da Pontuação Acumuladapara clientes Churn e não-Churn
sns.set(style="whitegrid")
# Definindo os limites para equalizações dos boxplot
lim_min_pontacum = math.floor(min(data['PontosAcumulados'])/ 100) * 100
lim_max_pontacum = math.ceil(max(data['PontosAcumulados']) / 100) * 100
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(16, 5))
# Box plot 1 (Clientes Churn)
sns.boxplot(ax=axes[0], x='PontosAcumulados', data=data[data['Churn'] == 1])
axes[0].set_title('Clientes Churn')
# Definir limites iguais para os eixos x
axes[0].set_xlim(lim_min_pontacum, lim_max_pontacum)
# Box plot 2 (Clientes Não Churn)
sns.boxplot(ax=axes[1], x='PontosAcumulados', data=data[data['Churn'] == 0])
axes[1].set_title('Clientes Não Churn')
# Definir limites iguais para os eixos x
axes[1].set_xlim(lim_min_pontacum, lim_max_pontacum)
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
Com exceção do primeiro grupo, em razão do pouco número de observações, as demais faixas exibem proporções semelhantes na relação Churn x Não Churn. Devido à proximidade entre os valores de cada grupo, é possível inferir que a variável "Pontos Acumulados" não parece ser uma característica determinante para a decisão do cliente de deixar a companhia.
As variáveis LOCALIZAÇÃO, GÊNERO e TIPOS DE CARTÃO são categóricas e a análise incial está sendo realizada para identicação destes dados e quais informações relevantes podem ser extraídas para o modelo futuramente
paises = data['Localizacao'].unique()
print(paises)
['France' 'Spain' 'Germany']
# Contando a quantidade de clientes por país
clientes_localizacao = data['Localizacao'].value_counts()
# Criando um DataFrame a partir dos valores contados
tb_localizacao = pd.DataFrame({'País': clientes_localizacao.index,
'Clientes': clientes_localizacao.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_localizacao['Clientes'].sum()
linha_total = pd.DataFrame({'País': ['Total'],
'Clientes': [total_clientes]})
tb_localizacao = pd.concat([tb_localizacao, linha_total], ignore_index=True)
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6))
frequencia_paises = data['Localizacao'].value_counts()
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_localizacao.values,
colLabels=tb_localizacao.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white'],
['white', 'white'],
['lightgray', 'lightgray']],
colLoc='center',
colWidths=[0.15, 0.15])
axes[0].set_title('Tabela de Países', fontsize=14)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=frequencia_paises.index, y=frequencia_paises.values, color='#657BA6', ax=axes[1])
axes[1].set_xlabel('País')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição dos Clientes por País', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(frequencia_paises.values):
axes[1].text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
# Agrupar os dados por localização e contar as ocorrências de Churn e Não Churn
grouped_Localizacao = data.groupby('Localizacao')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_Localizacao['Total'] = grouped_Localizacao[0] + grouped_Localizacao[1]
grouped_Localizacao['Não_Churn_Percent'] = (grouped_Localizacao[0] / grouped_Localizacao['Total']) * 100
grouped_Localizacao['Churn_Percent'] = (grouped_Localizacao[1] / grouped_Localizacao['Total']) * 100
# Renomear as colunas
grouped_Localizacao.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
# Resetar o índice para ter 'PontuacaoCredito_grupo' como uma coluna
grouped_Localizacao.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_Localizacao, headers='keys', tablefmt='pretty'))
+---+-------------+-----------+-------+-------+-------------------+--------------------+ | | Localizacao | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+-------------+-----------+-------+-------+-------------------+--------------------+ | 0 | France | 4203 | 811 | 5014 | 83.82528919026726 | 16.174710809732748 | | 1 | Germany | 1695 | 814 | 2509 | 67.55679553607014 | 32.44320446392985 | | 2 | Spain | 2064 | 413 | 2477 | 83.3266047638272 | 16.673395236172787 | +---+-------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados por PontuacaoCredito_grupo e Churn e contar as ocorrências
grouped_Localizacao = data.groupby(['Localizacao', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_Localizacao.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (12, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_Localizacao[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('País')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Localização')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
Esta variável implica que a localização dos clientes pode ter impacto na decisão de Churn, especialmente na Alemanha, onde uma proporção significativamente maior de clientes opta por sair. É possível destacar que a quantidade de saída de clientes na Alemanha é semelhante à observada na França, que concentra o dobro de clientes, necessitando, possivelmente, um balanceamento de dados num modelo futuro.
genero = data['Genero'].unique()
print(genero)
['Female' 'Male']
# Contando a quantidade de clientes
clientes_genero = data['Genero'].value_counts()
# Criando um DataFrame a partir dos valores contados
tb_genero = pd.DataFrame({'Gênero': clientes_genero.index,
'Clientes': clientes_genero.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_genero['Clientes'].sum()
linha_total = pd.DataFrame({'Gênero': ['Total'],
'Clientes': [total_clientes]})
tb_genero = pd.concat([tb_genero, linha_total], ignore_index=True)
frequencia_genero = data['Genero'].value_counts()
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6))
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_genero.values,
colLabels=tb_genero.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white'],
['lightgray', 'lightgray']],
colLoc='center',
colWidths=[0.15, 0.15])
axes[0].set_title('Tabela por Gênero', fontsize=14)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=frequencia_genero.index, y=frequencia_genero.values, color='#657BA6', ax=axes[1])
axes[1].set_xlabel('País')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição dos Clientes por Gênero', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(frequencia_genero.values):
axes[1].text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
# Agrupar os dados por genero e contar as ocorrências de Churn e Não Churn
grouped_genero = data.groupby('Genero')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_genero['Total'] = grouped_genero[0] + grouped_genero[1]
grouped_genero['Não_Churn_Percent'] = (grouped_genero[0] / grouped_genero['Total']) * 100
grouped_genero['Churn_Percent'] = (grouped_genero[1] / grouped_genero['Total']) * 100
# Renomear as colunas
grouped_genero.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_genero.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_genero, headers='keys', tablefmt='pretty'))
+---+--------+-----------+-------+-------+-------------------+--------------------+ | | Genero | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+--------+-----------+-------+-------+-------------------+--------------------+ | 0 | Female | 3404 | 1139 | 4543 | 74.92846136913933 | 25.071538630860662 | | 1 | Male | 4558 | 899 | 5457 | 83.52574674729705 | 16.47425325270295 | +---+--------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados por Genero e Churn e contar as ocorrências
grouped_genero = data.groupby(['Genero', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_genero.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (8, 4))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_genero[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Genero')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Genero')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
Embora a diferença entre as os gêneros masculino e feminino não seja significativa, é possível verificar que a uma tendência maior saída de cliente do sexo feminino.
cartao = data['TipoCartao'].unique()
print(cartao)
['DIAMOND' 'GOLD' 'SILVER' 'PLATINUM']
# Contando a quantidade de clientes
clientes_tipocartao = data['TipoCartao'].value_counts()
# Criando um DataFrame a partir dos valores contados
tb_tipocartao = pd.DataFrame({'Tipo Cartão': clientes_tipocartao.index,
'Clientes': clientes_tipocartao.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_tipocartao['Clientes'].sum()
linha_total = pd.DataFrame({'Tipo Cartão': ['Total'],
'Clientes': [total_clientes]})
tb_tipocartao = pd.concat([tb_tipocartao, linha_total], ignore_index=True)
frequencia_tipocartao = data['TipoCartao'].value_counts()
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6))
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_tipocartao.values,
colLabels=tb_tipocartao.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white'],
['white', 'white'],
['white', 'white'],
['lightgray', 'lightgray']],
colLoc='center',
colWidths=[0.3, 0.3])
axes[0].set_title('Tabela por Tipo de Cartão', fontsize=14)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=frequencia_tipocartao.index, y=frequencia_tipocartao.values, color='#657BA6', ax=axes[1])
axes[1].set_xlabel('País')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição dos Clientes por Tipo de Cartão', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(frequencia_tipocartao.values):
axes[1].text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_TipoCartao = data.groupby('TipoCartao')['Churn'].value_counts().unstack(fill_value=0)
# Calcular percentuais de Churn e Não Churn
grouped_TipoCartao['Total'] = grouped_TipoCartao[0] + grouped_TipoCartao[1]
grouped_TipoCartao['Não_Churn_Percent'] = (grouped_TipoCartao[0] / grouped_TipoCartao['Total']) * 100
grouped_TipoCartao['Churn_Percent'] = (grouped_TipoCartao[1] / grouped_TipoCartao['Total']) * 100
# Renomear as colunas
grouped_TipoCartao.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_TipoCartao.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_TipoCartao, headers='keys', tablefmt='pretty'))
+---+------------+-----------+-------+-------+-------------------+--------------------+ | | TipoCartao | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+------------+-----------+-------+-------+-------------------+--------------------+ | 0 | DIAMOND | 1961 | 546 | 2507 | 78.22098125249302 | 21.77901874750698 | | 1 | GOLD | 2020 | 482 | 2502 | 80.73541167066347 | 19.26458832933653 | | 2 | PLATINUM | 1987 | 508 | 2495 | 79.63927855711422 | 20.360721442885772 | | 3 | SILVER | 1994 | 502 | 2496 | 79.88782051282051 | 20.11217948717949 | +---+------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências
grouped_TipoCartao = data.groupby(['TipoCartao', 'Churn']).size().unstack(fill_value=0)
# Definir os rótulos das categorias e a largura das barras
categories = grouped_TipoCartao.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (16, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_TipoCartao[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Cartão')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Tipo de Cartão')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
Esta variável apresenta proporcionalidade semelhantes entre si, tanto nos serviços ofertados quanto na relação Churn x Não Churn.
As próximas variáveis apresentam dados referentes aos serviços prestados e também sobre o comportamento do cliente. Elas são identificados por 2 alternativas: 0 - Não e 1 - Sim
credito = data['TemCartaoCredito'].unique()
print(credito)
[1 0]
# Contando a quantidade de clientes com e sem cartão de crédito
clientes_cartao = data['TemCartaoCredito'].value_counts()
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
clientes_cartao.index = clientes_cartao.index.map({0: 'Não Possui', 1: 'Possui'})
# Criando um DataFrame a partir dos valores contados
tb_cartao = pd.DataFrame({'Tem Cartão?': clientes_cartao.index,
'Clientes': clientes_cartao.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_cartao['Clientes'].sum()
linha_total = pd.DataFrame({'Tem Cartão?': ['Total'],
'Clientes': [total_clientes]})
tb_cartao = pd.concat([tb_cartao, linha_total], ignore_index=True)
frequencia_cartao = data['TemCartaoCredito'].value_counts()
frequencia_cartao.index = frequencia_cartao.index.map({0: 'Não Possui', 1: 'Possui'})
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6))
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_cartao.values,
colLabels=tb_cartao.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white'],
['lightgray', 'lightgray']],
colLoc='center',
colWidths=[0.3, 0.3])
axes[0].set_title('Tabela', fontsize=16)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=frequencia_cartao.index, y=frequencia_cartao.values, color='#657BA6', ax=axes[1])
axes[1].set_xlabel('')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Cliente Possui Cartão de Crédito?', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(frequencia_cartao.values):
plt.text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_cartao = data.groupby('TemCartaoCredito')['Churn'].value_counts().unstack(fill_value=0)
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
grouped_cartao.index = grouped_cartao.index.map({0: 'Não Possui', 1: 'Possui'})
# Calcular percentuais de Churn e Não Churn
grouped_cartao['Total'] = grouped_cartao[0] + grouped_cartao[1]
grouped_cartao['Não_Churn_Percent'] = (grouped_cartao[0] / grouped_cartao['Total']) * 100
grouped_cartao['Churn_Percent'] = (grouped_cartao[1] / grouped_cartao['Total']) * 100
# Renomear as colunas
grouped_cartao.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_cartao.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_cartao, headers='keys', tablefmt='pretty'))
+---+------------------+-----------+-------+-------+-------------------+--------------------+ | | TemCartaoCredito | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+------------------+-----------+-------+-------+-------------------+--------------------+ | 0 | Não Possui | 2332 | 613 | 2945 | 79.18505942275043 | 20.814940577249573 | | 1 | Possui | 5630 | 1425 | 7055 | 79.80155917788802 | 20.198440822111976 | +---+------------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_cartao = data.groupby('TemCartaoCredito')['Churn'].value_counts().unstack(fill_value=0)
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
grouped_cartao.index = grouped_cartao.index.map({0: 'Não Possui', 1: 'Possui'})
# Definir os rótulos das categorias e a largura das barras
categories = grouped_cartao.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (8, 4))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_cartao[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Possui Cartão de Crédito?')
ax.set_ylabel('Quantidade')
ax.set_title('Churn')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
70,55% dos clientes possuem cartão de crédito, sendo possível visualizar que a taxa de Churn é semelhante entre as duas opções. Diante disso, a posse de um cartão de crédito não parece ser um fator significativo que influencia diretamente a decisão de dos clientes de sair da companhia.
credito = data['MembroAtivo'].unique()
print(credito)
[1 0]
# Contando a quantidade de clientes
clientes_ativo = data['MembroAtivo'].value_counts()
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
clientes_ativo.index = clientes_ativo.index.map({0: 'Não', 1: 'Sim'})
# Criando um DataFrame a partir dos valores contados
tb_ativo = pd.DataFrame({'É cliente ativo?': clientes_ativo.index,
'Clientes': clientes_ativo.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_ativo['Clientes'].sum()
linha_total = pd.DataFrame({'É cliente ativo?': ['Total'],
'Clientes': [total_clientes]})
tb_ativo = pd.concat([tb_ativo, linha_total], ignore_index=True)
frequencia_ativo = data['MembroAtivo'].value_counts()
frequencia_ativo.index = frequencia_ativo.index.map({0: 'Não', 1: 'Sim'})
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6))
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_ativo.values,
colLabels=tb_ativo.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white'],
['lightgray', 'lightgray']],
colLoc='center',
colWidths=[0.3, 0.3])
axes[0].set_title('Tabela', fontsize=16)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=frequencia_ativo.index, y=frequencia_ativo.values, color='#657BA6', ax=axes[1])
axes[1].set_xlabel('É cliente ativo?')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição dos Clientes por Atividade', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(frequencia_ativo.values):
plt.text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_ativo = data.groupby('MembroAtivo')['Churn'].value_counts().unstack(fill_value=0)
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
grouped_ativo.index = grouped_ativo.index.map({0: 'Não', 1: 'Sim'})
# Calcular percentuais de Churn e Não Churn
grouped_ativo['Total'] = grouped_ativo[0] + grouped_ativo[1]
grouped_ativo['Não_Churn_Percent'] = (grouped_ativo[0] / grouped_ativo['Total']) * 100
grouped_ativo['Churn_Percent'] = (grouped_ativo[1] / grouped_ativo['Total']) * 100
# Renomear as colunas
grouped_ativo.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_ativo.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_ativo, headers='keys', tablefmt='pretty'))
+---+-------------+-----------+-------+-------+-------------------+--------------------+ | | MembroAtivo | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+-------------+-----------+-------+-------+-------------------+--------------------+ | 0 | Não | 3546 | 1303 | 4849 | 73.12848009898948 | 26.871519901010515 | | 1 | Sim | 4416 | 735 | 5151 | 85.73092603377985 | 14.269073966220153 | +---+-------------+-----------+-------+-------+-------------------+--------------------+
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_ativo = data.groupby('MembroAtivo')['Churn'].value_counts().unstack(fill_value=0)
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
grouped_ativo.index = grouped_ativo.index.map({0: 'Não', 1: 'Sim'})
# Definir os rótulos das categorias e a largura das barras
categories = grouped_ativo.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (8, 4))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_ativo[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('É cliente ativo?')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Atividade')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
Verifica-se que a proporção entre clientes ativos e não ativos é equilibrada. Ser um membro ativo parece ser um fator importante na retenção de clientes, pois os clientes ativos têm uma taxa de Churn significativamente menor em comparação com os não membros ativos.
credito = data['Reclamacoes'].unique()
print(credito)
[1 0]
# Contando a quantidade de clientes
clientes_reclamacao = data['Reclamacoes'].value_counts()
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
clientes_reclamacao.index = clientes_reclamacao.index.map({0: 'Não', 1: 'Sim'})
# Criando um DataFrame a partir dos valores contados
tb_reclamacao = pd.DataFrame({'Teve Reclamação?': clientes_reclamacao.index,
'Clientes': clientes_reclamacao.values})
# Adicionando uma linha de total ao DataFrame
total_clientes = tb_reclamacao['Clientes'].sum()
linha_total = pd.DataFrame({'Teve Reclamação?': ['Total'],
'Clientes': [total_clientes]})
tb_cartao = pd.concat([tb_reclamacao, linha_total], ignore_index=True)
frequencia_reclamacao = data['Reclamacoes'].value_counts()
frequencia_reclamacao.index = frequencia_reclamacao.index.map({0: 'Não', 1: 'Sim'})
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6))
# Plotando a tabela tb_churn no primeiro subplot
axes[0].axis('off')
axes[0].table(cellText=tb_reclamacao.values,
colLabels=tb_reclamacao.columns,
cellLoc='center',
loc='center',
colColours=['lightgray', 'lightgray'],
cellColours=[['white', 'white'],
['white', 'white']],
colLoc='center',
colWidths=[0.3, 0.3])
axes[0].set_title('Tabela', fontsize=16)
# Gráfico de barras para a distribuição original no segundo subplot
sns.barplot(x=frequencia_reclamacao.index, y=frequencia_reclamacao.values, color='#657BA6', ax=axes[1])
axes[1].set_xlabel('Teve Reclamação?')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição dos Clientes por Reclamação', fontsize=14)
# Adicionando os rótulos de dados no gráfico de barras
for index, value in enumerate(frequencia_reclamacao.values):
plt.text(index, value, str(value), ha='center', va='bottom')
plt.tight_layout()
plt.show()
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_reclamacao = data.groupby('Reclamacoes')['Churn'].value_counts().unstack(fill_value=0)
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
grouped_reclamacao.index = grouped_reclamacao.index.map({0: 'Não', 1: 'Sim'})
# Calcular percentuais de Churn e Não Churn
grouped_reclamacao['Total'] = grouped_reclamacao[0] + grouped_reclamacao[1]
grouped_reclamacao['Não_Churn_Percent'] = (grouped_reclamacao[0] / grouped_reclamacao['Total']) * 100
grouped_reclamacao['Churn_Percent'] = (grouped_reclamacao[1] / grouped_reclamacao['Total']) * 100
# Renomear as colunas
grouped_reclamacao.columns = ['Não Churn', 'Churn', 'Total', 'Freq Não Churn', 'Freq Churn']
grouped_reclamacao.reset_index(inplace=True)
# Exibir a tabela
print(tabulate(grouped_reclamacao, headers='keys', tablefmt='pretty'))
+---+-------------+-----------+-------+-------+--------------------+----------------------+ | | Reclamacoes | Não Churn | Churn | Total | Freq Não Churn | Freq Churn | +---+-------------+-----------+-------+-------+--------------------+----------------------+ | 0 | Não | 7952 | 4 | 7956 | 99.94972347913524 | 0.050276520864756154 | | 1 | Sim | 10 | 2034 | 2044 | 0.4892367906066536 | 99.51076320939335 | +---+-------------+-----------+-------+-------+--------------------+----------------------+
# Agrupar os dados e contar as ocorrências de Churn e Não Churn
grouped_reclamacao = data.groupby('Reclamacoes')['Churn'].value_counts().unstack(fill_value=0)
# Substituindo os valores 0 e 1 pelos rótulos correspondentes
grouped_reclamacao.index = grouped_reclamacao.index.map({0: 'Não', 1: 'Sim'})
# Definir os rótulos das categorias e a largura das barras
categories = grouped_reclamacao.index
x = np.arange(len(categories))
width = 0.4
# Criar o gráfico de barras agrupadas
fig, ax = plt.subplots(figsize = (8, 6))
for i, (churn_label, color) in enumerate(zip([0, 1], ['darkblue', 'tomato'])):
values = grouped_reclamacao[churn_label].values
ax.bar(x + i * width, values, width, label=churn_label, color=color)
# Personalizar o gráfico
ax.set_xlabel('Teve Reclamação')
ax.set_ylabel('Quantidade')
ax.set_title('Churn por Reclamação')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(categories, rotation=0)
# Adicionar legenda
legend_labels = ['Não Churn', 'Churn']
ax.legend((legend_labels), title='Churn')
plt.tight_layout()
plt.show()
Nesta variável podemos destacar que a presença de reclamações por parte dos clientes é um indicador forte de Churn, com uma taxa extremamente alta de Churn entre esses clientes.
Além de analisar o comportamento das variáveis em relação ao Churn, também investigaremos como essas variáveis interagem entre si. Utilizamos mapas de calor (Heat Maps) como uma representação gráfica para entender a correlação entre as variáveis.
É possível que a maioria das variáveis não apresente uma relação significativa ou a relação seja levemente negativa, indicando independência entre elas. A relação entre Churn e Reclamações já foi discutida anteriormente, revelando que as reclamações dos clientes influenciam o Churn, enquanto clientes sem reclamações tendem a permanecer na empresa. É importante observar que esse comportamento é compartilhado por todas as variáveis em relação ao Churn, devido a essa característica específica.
plt.figure(figsize=(16, 6))
correlations = data.corr()
sns.heatmap(correlations,
cmap="Reds", # Blues, Reds, Oranges, Greens, Purples, Greys
annot=True,
fmt='.4f')
plt.title('Matriz de Correlação') # Adiciona título ao gráfico
plt.show()
Após finalizada a Exploração dos Dados, será definido o tratamento dos dados. Observamos que o conjunto de dados não continha Dados Faltantes, contudo, visualizamos, pelos boxplot, que algumas variáveis contém outliers. Vamos analisar e fazer o devido tratamento dos mesmos.
Os outliers são valores atípicos, que se afastam significativamente do padrão geral do conjunto de dados. Eles são observações que são extremamente diferentes da maioria das outras amostras e podem ter um impacto desproporcional nas análises estatísticas. Identificar e entender os outliers é crucial em análise de dados, pois eles podem distorcer resultados, prejudicar a precisão dos modelos estatísticos e influenciar decisões errôneas.
Primeiramente vamos procurar identificar os outliers do nosso conjunto de dados, através do boxplot, para após fazer a eliminação de dados que julgamos ser necessário para o andamento de nosso modelo de dados.
# Definir o número de linhas e colunas
linhas = 2
colunas = 3
# Criar uma figura e eixos para os subplots
fig, axes = plt.subplots(nrows=linhas, ncols=colunas, figsize=(16, 4))
# Lista das variáveis que você deseja plotar
variaveis = ['PontuacaoCredito', 'Idade', 'Saldo', 'SalarioEstimado', 'PontosAcumulados']
# Loop sobre as variáveis e os subplots correspondentes
for i, var in enumerate(variaveis):
# Box plot para cada variável
sns.boxplot(ax=axes[i//colunas, i%colunas], x=data[var])
axes[i//colunas, i%colunas].set_title(var) # Definir o título do subplot
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
Iremos fazer a detecção e remoção de outliers através da técnica de IQR score (interquartile range). O IQR é o primeiro quartil subtraído do terceiro quartil.
Conforme análise realizada e visualizando dados que possam interferir em nosso modelo, definimos, neste momento, que as variáveis PONTUAÇÃO CRÉDITO, IDADE e PONTOS ACUMULADOS são as que possuem maior interferência nos dados, e, assim sendo, eliminaremos para melhor balancear o nosso modelo.
# Calcular o IQR para as variáveis numéricas
Q1 = data[['PontuacaoCredito', 'Idade', 'PontosAcumulados']].quantile(0.25)
Q3 = data[['PontuacaoCredito', 'Idade', 'PontosAcumulados']].quantile(0.75)
IQR = Q3 - Q1
# Identificar os outliers usando IQR
outliers = ((data[['PontuacaoCredito', 'Idade', 'PontosAcumulados']] < (Q1 - 1.5 * IQR)) |
(data[['PontuacaoCredito', 'Idade', 'PontosAcumulados']] > (Q3 + 1.5 * IQR))).any(axis=1)
# Remover os outliers
data_sem_outliers = data[~outliers]
Iremos novamente exibir a representação gráfica das 3 variáveis tratadas, para identificar o resultado da aplicação.
# Definir o número de linhas e colunas
linhas = 2
colunas = 3
# Criar uma figura e eixos para os subplots
fig, axes = plt.subplots(nrows=linhas, ncols=colunas, figsize=(16, 6))
# Lista das variáveis que você deseja plotar
variaveis = ['PontuacaoCredito', 'Idade', 'PontosAcumulados']
# Loop sobre as variáveis e os subplots correspondentes
for i, var in enumerate(variaveis):
# Box plot para cada variável (linha 1)
sns.boxplot(ax=axes[0, i], x=data_sem_outliers[var])
axes[0, i].set_title(f'Boxplot de {var}') # Definir o título do subplot
# Histograma para cada variável (linha 2)
sns.histplot(data_sem_outliers[var], bins=30, kde=True, ax=axes[1, i], color='orange')
axes[1, i].set_title(f'Histograma de {var}') # Definir o título do subplot
# Ajustar o layout
plt.tight_layout()
# Exibir o gráfico
plt.show()
# Filtrar os dados onde 'PontosAcumulados_grupo' está na faixa de 101 a 200
data_filtrado = data_sem_outliers[data_sem_outliers['PontosAcumulados_grupo'] == '101 - 200']
# Exibir os dados filtrados
data_filtrado.head()
| PontuacaoCredito | Localizacao | Genero | Idade | TempoPermanencia | Saldo | Produtos | TemCartaoCredito | MembroAtivo | SalarioEstimado | Churn | Reclamacoes | IndiceSatisfacao | TipoCartao | PontosAcumulados | PontuacaoCredito_grupo | Idade_grupo | Saldo_grupo | Salario_grupo | PontosAcumulados_grupo | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12 | 476 | France | Female | 34 | 10 | 0.00 | 2 | 1 | 0 | 26260.98 | 0 | 0 | 3 | SILVER | 119 | 451 - 500 | 31 - 40 | 0 - 24999 | 20000 - 39999 | 101 - 200 |
| 16 | 653 | Germany | Male | 58 | 1 | 132602.88 | 1 | 1 | 0 | 5097.67 | 1 | 0 | 2 | SILVER | 163 | 651 - 700 | 51 - 60 | 125000 - 149999 | 0 - 19999 | 101 - 200 |
Foi possível identificar que a variável Pontos Acumulados ainda permanece com dados dos clientes no 1º faixa exibida. Por ser um quantidade insignificante na nossa base de dados, optaremos pela exclusão manual destes dados. Aproveitamos, também, para eliminar os dados de clientes que possuam 4 serviços contratados, uma vez que desta variável gerou somente em resultados negativos no Churn da companhia.
# Filtrar as linhas onde PontosAcumulados_grupo está na faixa de 101 a 200
excluir_pontosacum = data_sem_outliers[data_sem_outliers['PontosAcumulados_grupo'] == '101 - 200'].index
# Excluir as linhas do DataFrame
data2 = data_sem_outliers.drop(excluir_pontosacum)
## Grafico
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 4))
# Box plot 1 (Clientes Churn)
sns.boxplot(ax=axes[0], x='PontosAcumulados', data=data2)
axes[0].set_title('PontosAcumulados', fontsize=14)
# Gráfico de barras para a distribuição original no segundo subplot
sns.histplot(data2['PontosAcumulados'], bins=30, kde=True, ax=axes[1], color='orange')
axes[1].set_xlabel('Pontuação Acumulada')
axes[1].set_ylabel('Frequência')
axes[1].set_title('Distribuição da Pontuação Acumulada', fontsize=14)
plt.tight_layout()
plt.show()
excluir_produtos = data2[data2['Produtos'] == 4].index
# Excluir as linhas do DataFrame
data2 = data_sem_outliers.drop(excluir_produtos)
data2.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 9568 entries, 0 to 9999 Data columns (total 20 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PontuacaoCredito 9568 non-null int64 1 Localizacao 9568 non-null object 2 Genero 9568 non-null object 3 Idade 9568 non-null int64 4 TempoPermanencia 9568 non-null int64 5 Saldo 9568 non-null float64 6 Produtos 9568 non-null int64 7 TemCartaoCredito 9568 non-null int64 8 MembroAtivo 9568 non-null int64 9 SalarioEstimado 9568 non-null float64 10 Churn 9568 non-null int64 11 Reclamacoes 9568 non-null int64 12 IndiceSatisfacao 9568 non-null int64 13 TipoCartao 9568 non-null object 14 PontosAcumulados 9568 non-null int64 15 PontuacaoCredito_grupo 9568 non-null category 16 Idade_grupo 9568 non-null category 17 Saldo_grupo 9568 non-null category 18 Salario_grupo 9568 non-null category 19 PontosAcumulados_grupo 9568 non-null category dtypes: category(5), float64(2), int64(10), object(3) memory usage: 1.2+ MB
Na variável SALDO EM CONTA observamos que muitos valores são zeros o que não é ideal para o nosso modelo de trabalho. Desta forma, precisamos fazer uma calibração destes valores
saldo_zeros_count = (data2['Saldo'] == 0).sum()
print("Número de valores zero na variável 'Saldo':", saldo_zeros_count)
zeros = (data2['Saldo'] == 0)
data2[zeros].sort_values(by='Idade')
Número de valores zero na variável 'Saldo': 3465
| PontuacaoCredito | Localizacao | Genero | Idade | TempoPermanencia | Saldo | Produtos | TemCartaoCredito | MembroAtivo | SalarioEstimado | Churn | Reclamacoes | IndiceSatisfacao | TipoCartao | PontosAcumulados | PontuacaoCredito_grupo | Idade_grupo | Saldo_grupo | Salario_grupo | PontosAcumulados_grupo | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 8522 | 644 | Spain | Male | 18 | 8 | 0.0 | 2 | 1 | 0 | 59172.42 | 0 | 0 | 4 | DIAMOND | 975 | 601 - 650 | 11 - 20 | 0 - 24999 | 40000 - 59999 | 901 - 1000 |
| 2141 | 674 | France | Male | 18 | 7 | 0.0 | 2 | 1 | 1 | 55753.12 | 1 | 1 | 5 | DIAMOND | 603 | 651 - 700 | 11 - 20 | 0 - 24999 | 40000 - 59999 | 601 - 700 |
| 4716 | 646 | France | Male | 18 | 10 | 0.0 | 2 | 0 | 1 | 52795.15 | 0 | 0 | 5 | GOLD | 938 | 601 - 650 | 11 - 20 | 0 - 24999 | 40000 - 59999 | 901 - 1000 |
| 7334 | 616 | France | Male | 18 | 6 | 0.0 | 2 | 1 | 1 | 27308.58 | 0 | 0 | 3 | PLATINUM | 466 | 601 - 650 | 11 - 20 | 0 - 24999 | 20000 - 39999 | 401 - 500 |
| 9572 | 644 | Spain | Male | 18 | 7 | 0.0 | 1 | 0 | 1 | 59645.24 | 1 | 1 | 1 | GOLD | 620 | 601 - 650 | 11 - 20 | 0 - 24999 | 40000 - 59999 | 601 - 700 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1996 | 534 | France | Male | 62 | 2 | 0.0 | 2 | 0 | 0 | 42763.12 | 1 | 1 | 2 | SILVER | 589 | 501 - 550 | 61 - 70 | 0 - 24999 | 40000 - 59999 | 501 - 600 |
| 387 | 730 | Spain | Male | 62 | 2 | 0.0 | 2 | 1 | 1 | 186489.95 | 0 | 0 | 4 | GOLD | 594 | 701 - 750 | 61 - 70 | 0 - 24999 | 180000 - 199999 | 501 - 600 |
| 9279 | 727 | France | Male | 62 | 5 | 0.0 | 2 | 0 | 1 | 38652.96 | 0 | 0 | 5 | GOLD | 599 | 701 - 750 | 61 - 70 | 0 - 24999 | 20000 - 39999 | 501 - 600 |
| 4157 | 850 | Spain | Male | 62 | 5 | 0.0 | 2 | 1 | 1 | 180243.56 | 0 | 0 | 5 | GOLD | 324 | 801 - 850 | 61 - 70 | 0 - 24999 | 180000 - 199999 | 301 - 400 |
| 5577 | 592 | France | Female | 62 | 5 | 0.0 | 1 | 1 | 1 | 100941.57 | 0 | 0 | 1 | PLATINUM | 938 | 551 - 600 | 61 - 70 | 0 - 24999 | 100000 - 119999 | 901 - 1000 |
3465 rows × 20 columns
Optamos por calcular as média dos salários por idade que não sejam valores zeros. Desta forma, todos os valores igual a zero, serão substituídos pela média calculada, trazendo um dado mais balanceado para a nossa amostra
# Calculando a média do saldo para as idades não zero
saldo_por_idade = data2[data2['Idade'] != 0].groupby('Idade')['Saldo'].mean()
# Função para substituir os valores zero na coluna 'Saldo' pelo valor médio correspondente à idade
def substituir_saldo(row):
if row['Saldo'] == 0:
return saldo_por_idade[row['Idade']]
return row['Saldo']
# Aplicando a função à coluna 'Saldo' para criar a nova coluna 'Saldo2'
data2['Saldo2'] = data2.apply(substituir_saldo, axis=1)
# Criando subplots
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 4))
# Gráfico de dispersão para 'Idade' vs 'Saldo' com coloração por 'Churn'
sns.scatterplot(data=data2, x='Idade', y='Saldo', hue='Churn', palette=['darkblue', 'tomato'], ax=axes[0])
axes[0].set_title('Idade vs Saldo (Original)', fontsize=14)
# Gráfico de dispersão para 'Idade' vs 'Saldo2' com coloração por 'Churn'
sns.scatterplot(data=data2, x='Idade', y='Saldo2', hue='Churn', palette=['darkblue', 'tomato'], ax=axes[1])
axes[1].set_title('Idade vs Saldo (Corrigido)', fontsize=14)
plt.tight_layout()
plt.show()
Nesta seção, serão abordados o processo de construção do modelo de Machine Learning para prever a Churn dos clientes. A construção eficaz do modelo permite transformar insights e padrões identificados anteriormente em um sistema preditivo robusto.
Utilizaremos a biblioteca Scikit-Learn para implementar um modelo de Random Forest, que se destaca na previsão de padrões complexos em conjuntos de dados.
# Separando features e variável alvo
X = data2.drop(['Churn', 'Reclamacoes', 'Saldo', 'PontuacaoCredito_grupo', 'Idade_grupo',
'Saldo_grupo', 'Salario_grupo', 'PontosAcumulados_grupo'], axis=1)
y = data2['Churn']
# Definindo variáveis numéricas e categóricas
var_numerica = ['PontuacaoCredito', 'Idade', 'TempoPermanencia', 'Produtos',
'IndiceSatisfacao', 'SalarioEstimado', 'Saldo2', 'PontosAcumulados']
var_categorica = ['Localizacao', 'Genero', 'TemCartaoCredito', 'MembroAtivo',
'TipoCartao']
# Criando transformadores para variáveis numéricas e categóricas
trans_numerica = Pipeline(steps=[
('scaler', StandardScaler())
])
trans_categorica = Pipeline(steps=[
('onehot', OneHotEncoder(drop='first', sparse=False, handle_unknown='ignore'))
])
# Aplicando transformadores às variáveis correspondentes
preprocessor = ColumnTransformer(
transformers=[
('num', trans_numerica, var_numerica),
('cat', trans_categorica, var_categorica)
])
# Criando o modelo de classificação RandomForest
model = RandomForestClassifier(n_estimators=100, random_state=42)
# Criando o pipeline com pré-processamento e modelo
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('model', model)
])
# Criando o objeto KFold para validação cruzada com 5 folds e embaralhamento dos dados
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
# Aplicando a validação cruzada e calculando a pontuação do modelo
scores = cross_val_score(pipeline, X, y, cv=kfold)
# Dividindo o conjunto de dados em treino e teste
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.3,
random_state=42)
# Treinando o modelo
pipeline.fit(X_train, y_train)
# Fazendo previsões no conjunto de teste
y_pred = pipeline.predict(X_test)
# Calculando a acurácia do modelo
accuracy = accuracy_score(y_test, y_pred)
# Gerando o relatório de classificação
report = classification_report(y_test, y_pred)
# Calculando a matriz de confusão
conf_matrix = confusion_matrix(y_test, y_pred)
# Imprimindo a acurácia e o relatório de classificação
print("Acurácia Média: %.2f%%" % (scores.mean() * 100))
print("Desvio Padrão: %.2f%%" % (scores.std() * 100))
print('Relatório de Classificação:\n', report)
# Criando um heatmap para a matriz de confusão
plt.figure(figsize=(8, 4))
sns.heatmap(conf_matrix, annot=True, fmt='g', cmap='Reds',
xticklabels=['Não Churn', 'Churn'], yticklabels=['Não Churn', 'Churn'])
plt.xlabel('Predição')
plt.ylabel('Atual')
plt.title('Matriz de Confusão')
plt.show()
Acurácia Média: 85.80%
Desvio Padrão: 0.49%
Relatório de Classificação:
precision recall f1-score support
0 0.87 0.97 0.92 2295
1 0.78 0.43 0.56 576
accuracy 0.86 2871
macro avg 0.83 0.70 0.74 2871
weighted avg 0.85 0.86 0.85 2871
O modelo construído apresenta uma acurácia é de 85%, o que indica um modelo satisfatório.No entanto, ao verificar as métricas de precisão e recall para a classe Churn, percebemos que há uma discrepância significativa.
O recall relativamente baixo indica que o modelo tem dificuldade em identificar adequadamente os clientes que realmente vão churn (casos positivos). Em outras palavras, ele está perdendo uma parte significativa dos clientes que sairão da empresa.
Desta forma, eliminaremos algumas variáveis categóricas através das análises de variância (ANOVA) para entender se há diferenças significativas entre grupos. Variáveis que não mostram diferenças significativas serão eliminadas.
for category in var_categorica:
group_labels = []
for group in data2[category].unique():
group_labels.append(data2[data2[category] == group]['Churn'])
f_stat, p_value = f_oneway(*group_labels)
if p_value > 0.05:
print(f'Variável {category} não mostra diferenças significativas.')
Variável TemCartaoCredito não mostra diferenças significativas. Variável TipoCartao não mostra diferenças significativas.
# Separando features e variável alvo
X = data2.drop(['Churn', 'Reclamacoes', 'PontuacaoCredito_grupo', 'Idade_grupo',
'Saldo_grupo', 'Salario_grupo', 'PontosAcumulados_grupo',
'TemCartaoCredito', 'TipoCartao', 'Saldo'], axis=1)
y = data2['Churn']
# Definindo variáveis numéricas e categóricas
var_numerica = ['PontuacaoCredito', 'Idade', 'TempoPermanencia', 'Produtos',
'IndiceSatisfacao', 'SalarioEstimado', 'PontosAcumulados', 'Saldo2','MembroAtivo']
var_categorica = ['Localizacao', 'Genero']
# Criando transformadores para variáveis numéricas e categóricas
trans_numerica = Pipeline(steps=[
('scaler', StandardScaler())
])
trans_categorica = Pipeline(steps=[
('onehot', OneHotEncoder(drop='first', sparse=False, handle_unknown='ignore'))
])
# Aplicando transformadores às variáveis correspondentes
preprocessor = ColumnTransformer(
transformers=[
('num', trans_numerica, var_numerica),
('cat', trans_categorica, var_categorica)
])
# Criando o modelo de classificação RandomForest
model = RandomForestClassifier(n_estimators=100, random_state=42)
# Criando o pipeline com pré-processamento e modelo
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('model', model)
])
# Criando o objeto KFold para validação cruzada com 5 folds e embaralhamento dos dados
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
# Aplicando a validação cruzada e calculando a pontuação do modelo
scores = cross_val_score(pipeline, X, y, cv=kfold)
# Dividindo o conjunto de dados em treino e teste
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2,
random_state=42)
# Treinando o modelo
pipeline.fit(X_train, y_train)
# Fazendo previsões no conjunto de teste
y_pred = pipeline.predict(X_test)
# Calculando a acurácia do modelo
accuracy = accuracy_score(y_test, y_pred)
# Gerando o relatório de classificação
report = classification_report(y_test, y_pred)
# Calculando a matriz de confusão
conf_matrix = confusion_matrix(y_test, y_pred)
# Imprimindo a acurácia e o relatório de classificação
print("Acurácia Média: %.2f%%" % (scores.mean() * 100))
print("Desvio Padrão: %.2f%%" % (scores.std() * 100))
print('Relatório de Classificação:\n', report)
# Criando um heatmap para a matriz de confusão
plt.figure(figsize=(8, 4))
sns.heatmap(conf_matrix, annot=True, fmt='g', cmap='Reds',
xticklabels=['Não Churn', 'Churn'], yticklabels=['Não Churn', 'Churn'])
plt.xlabel('Predição')
plt.ylabel('Atual')
plt.title('Matriz de Confusão')
plt.show()
Acurácia Média: 85.91%
Desvio Padrão: 0.56%
Relatório de Classificação:
precision recall f1-score support
0 0.87 0.97 0.92 1523
1 0.79 0.43 0.56 391
accuracy 0.86 1914
macro avg 0.83 0.70 0.74 1914
weighted avg 0.85 0.86 0.84 1914
A clusterização é um robusto método de análise para machine learning e para projetos de Data Science. Ao contrário de outros algoritmos que dependem de supervisão, a clusterização explora padrões nos dados de forma não supervisionada. Essa abordagem permite identificar grupos naturais e estruturas subjacentes nos conjuntos de dados, proporcionando insights valiosos para as empresas na tomada de decisões.
No entanto, antes de aplicar a clusterização, precisamos lidar com variáveis categóricas, pois a maioria dos algoritmos de clusterização trabalha com dados numéricos.
dummies = pd.get_dummies(data2[['Localizacao', 'Genero']])
#data3 = pd.concat([data2, dummies], axis = 1)
data3 = data2.drop(['Localizacao', 'Genero', 'TipoCartao', 'Saldo',
'PontuacaoCredito_grupo', 'Idade_grupo', 'Saldo_grupo',
'Salario_grupo', 'PontosAcumulados_grupo',
'Reclamacoes', 'TemCartaoCredito', 'MembroAtivo'], axis = 1)
data3.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 9568 entries, 0 to 9999 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PontuacaoCredito 9568 non-null int64 1 Idade 9568 non-null int64 2 TempoPermanencia 9568 non-null int64 3 Produtos 9568 non-null int64 4 SalarioEstimado 9568 non-null float64 5 Churn 9568 non-null int64 6 IndiceSatisfacao 9568 non-null int64 7 PontosAcumulados 9568 non-null int64 8 Saldo2 9568 non-null float64 dtypes: float64(2), int64(7) memory usage: 747.5 KB
Após isto, podemos agrupar os nossos dados para a padronização das variáveis.
# Crie uma cópia do DataFrame original
X1 = data3.drop('Churn', axis=1).copy()
y1 = data3['Churn']
# Ajuste o StandardScaler aos dados de treinamento (X1)
scaler = StandardScaler().fit(X1)
# Transforme os dados usando o scaler
X1_scaled = scaler.transform(X1)
# Crie um DataFrame a partir dos dados padronizados (X1_scaled)
X1_scaled_df = pd.DataFrame(X1_scaled, columns=X1.columns)
# Visualize as primeiras linhas do DataFrame padronizado
X1_scaled_df.head(10)
| PontuacaoCredito | Idade | TempoPermanencia | Produtos | SalarioEstimado | IndiceSatisfacao | PontosAcumulados | Saldo2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | -0.329808 | 0.485534 | -1.044203 | -0.938614 | 0.021813 | -0.725049 | -0.628321 | -0.833747 |
| 1 | -0.444286 | 0.371671 | -1.390497 | -0.938614 | 0.216419 | -0.012804 | -0.663740 | -0.628239 |
| 2 | -1.547436 | 0.485534 | 1.033562 | 2.699489 | 0.240567 | -0.012804 | -1.013503 | 1.736313 |
| 3 | 0.502758 | 0.143947 | -1.390497 | 0.880438 | -0.108963 | 1.411686 | -1.133042 | -0.931954 |
| 4 | 2.074226 | 0.599396 | -1.044203 | -0.938614 | -0.365266 | 1.411686 | -0.800988 | 0.671761 |
| 5 | -0.059224 | 0.713259 | 1.033562 | 0.880438 | 0.863396 | 1.411686 | -0.539773 | 0.305323 |
| 6 | 1.782828 | 1.396434 | 0.687268 | 0.880438 | -1.565220 | -0.725049 | -1.770585 | -0.519541 |
| 7 | -1.557843 | 0.713259 | -0.351615 | 0.880438 | -0.437304 | -0.012804 | -1.571353 | 1.187368 |
| 8 | 0.346652 | -1.222403 | -1.044203 | -0.938614 | -0.493193 | -0.012804 | -1.168461 | 0.955217 |
| 9 | -1.276852 | -0.766953 | 0.340974 | 0.880438 | -0.346194 | -0.012804 | -1.513797 | -0.060617 |
Criando os clusters via k-means, variando os valores de k.
A silhueta é uma métrica que mede o quão semelhante um objeto é ao seu próprio cluster em comparação com outros clusters. Ela varia de -1 a 1, onde um valor alto indica que o objeto está bem ajustado ao seu próprio cluster e mal ajustado aos clusters vizinhos.
# Defina uma faixa de valores de k
k_values = range(2, 8)
# Inicialize uma figura de subplots com tamanho maior
fig, axs = plt.subplots(2, 3, figsize=(16, 10))
axs = axs.ravel()
# Tamanho de fonte personalizado para os valores dos eixos
tick_font_size = 8
# Tamanho de fonte personalizado para os rótulos dos eixos
axis_font_size = 12
# Loop através dos valores de k
for i, k in enumerate(k_values):
# Aplique o algoritmo K-Means
kmeans = KMeans(n_clusters=k, random_state=42)
cluster_labels = kmeans.fit_predict(X1_scaled_df)
# Calcule a silhueta para cada amostra
sample_silhouette_values = silhouette_samples(X1_scaled_df, cluster_labels)
silhouette_avg = np.mean(sample_silhouette_values) # Média da silhueta para este k
# Crie um subplot para o gráfico de silhueta
axs[i].set_xlim([-0.2, 0.6])
axs[i].set_ylim([0, len(X1_scaled_df) + (k + 1) * 10])
y_lower = 10
for j in range(k):
# Agregue as pontuações de silhueta para amostras pertencentes a este cluster
jth_cluster_silhouette_values = sample_silhouette_values[cluster_labels == j]
jth_cluster_silhouette_values.sort()
size_cluster_j = jth_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_j
color = plt.cm.nipy_spectral(float(j) / k)
axs[i].fill_betweenx(np.arange(y_lower, y_upper),
0, jth_cluster_silhouette_values,
facecolor=color, edgecolor=color, alpha=0.7)
# Rotule o gráfico de silhueta com sua numeração de cluster no meio
axs[i].text(-0.05, y_lower + 0.5 * size_cluster_j, str(j), fontsize=tick_font_size)
# Calcule a nova posição de y_lower para o próximo gráfico
y_lower = y_upper + 10
axs[i].set_title(f'Silhueta para k={k}', fontsize=axis_font_size)
axs[i].set_xlabel("Valor da silhueta", fontsize=axis_font_size)
axs[i].set_ylabel("Cluster", fontsize=axis_font_size)
# Ajuste o tamanho da fonte dos valores dos eixos x e y
axs[i].tick_params(axis='both', labelsize=tick_font_size)
# Adicione uma linha vertical para a média da silhueta
axs[i].axvline(x=silhouette_avg, color="red", linestyle="--")
# Adicione uma barra de erro para a média da silhueta
axs[i].fill_betweenx([0, len(X1_scaled_df) + (k + 1) * 10],
silhouette_avg - np.std(sample_silhouette_values),
silhouette_avg + np.std(sample_silhouette_values), color='red', alpha=0.2)
# Rotule o gráfico com os valores da silhueta média
axs[i].text(silhouette_avg + 0.02, len(X1_scaled_df) + (k + 1) * 10 - 5, f'{silhouette_avg:.2f}',
fontsize=tick_font_size, color='red')
plt.tight_layout()
plt.show()
Os gráficos exibem linhas pontilhadas que representam a média da silhueta para o valor de cada cluster. Quanto mais próxima de 1, melhor definida está a separação. Enquanto que a área vermelha sombreada indica uma barra de erro, indicando a variabilidade para cada valor de k. Deste modo, quanto menor a variabilidade, melhor o modelo. Diante disso, destaca-se que a média da silhueta apresenta-se de forma homogênea entre cada k gerado, porém é possível verificar que os gráficos com 4, 5 e 7 cluster apresentam uma barra de erro maior, do qual observa-se que tem uma cauda deslocando-se à esquerda do 0. Isso indica que há alguns pontos de dados em clusters nessas configurações que não estão bem ajustados e estão mais próximos dos clusters vizinhos do que do próprio cluster. Isso pode ser um sinal de que esses números de clusters não são ideais para a separação dos dados.
# Defina uma faixa de valores de k
k_values = range(2, 8)
# Inicialize uma figura de subplots com tamanho maior
fig, axs = plt.subplots(2, 3, figsize=(16, 8))
axs = axs.ravel()
# Loop através dos valores de k
for i, k in enumerate(k_values):
# Aplique o algoritmo K-Means
kmeans = KMeans(n_clusters=k, random_state=42)
cluster_labels = kmeans.fit_predict(X1_scaled_df)
# Reduza a dimensionalidade usando PCA para visualização em 2D
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X1_scaled_df)
# Visualize os clusters em 2D usando PCA
axs[i].scatter(X_pca[:, 0], X_pca[:, 1], c=cluster_labels, cmap='viridis', edgecolor='k')
axs[i].set_title(f'Clusters para k={k}')
axs[i].set_xticks([])
axs[i].set_yticks([])
plt.show()
Na representação gráfica acima, é possível inferir que quanto menos clusters no modelo, maior é a separação entre os clusters, indicando uma maior clareza na formação de grupos distintos. Isso é um indicativo da eficácia do modelo na criação de grupos bem definidos. Por outro lado, à medida que a faixa de valores de k aumenta, ocorre a sobreposição dos clusters. Essa sobreposição não é ideal, pois indica que os clusters não estão bem distintos e os pontos de dados estão sendo atribuídos a múltiplos clusters simultaneamente. Portanto, é importante escolher um número adequado de clusters para garantir uma separação clara e significativa entre os grupos formados pelo algoritmo K-Means.
Nesta etapa, vamos avaliar o número ideal de clusters por meio da visualização dos dados utilizando o algoritmo t-SNE. Apesar de considerarmos outras abordagens, como o DBSCAN, é importante notar que essa técnica requer uma habilidade maior na especificação dos hiperparâmetros, tornando a análise mais complexa.
# Defina os valores de k que deseja testar
k_values = [2, 3, 4, 5, 6, 7]
# Inicialize o Plotly Subplots com duas linhas e três colunas
fig = make_subplots(rows=2, cols=3, subplot_titles=[f'T-SNE Plot (k={k})' for k in k_values])
# Loop através dos valores de k
for i, k in enumerate(k_values):
# Aplicar K-Means
kmeans = KMeans(n_clusters=k, random_state=42)
cluster_labels = kmeans.fit_predict(X1_scaled_df)
# Aplicar t-SNE
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(X1_scaled_df)
# Criar DataFrame para o t-SNE plot
tsne_df = pd.DataFrame(data=X_tsne, columns=['Componente 1', 'Componente 2'])
tsne_df['Cluster'] = cluster_labels
# Criar o Scatter Plot com as cores dos clusters
scatter = px.scatter(
tsne_df, x='Componente 1', y='Componente 2',
color='Cluster', title=f'T-SNE Plot (k={k})'
)
# Calcular a linha e coluna no subplot
row = i // 3 + 1
col = i % 3 + 1
# Adicionar o Scatter Plot ao subplot correspondente
fig.add_trace(
scatter['data'][0], # Extrair os dados do gráfico do Plotly Express
row=row, col=col
)
# Atualizar o layout para melhor visualização
fig.update_layout(height=600, width=800)
# Exibir o gráfico
fig.show()
Com base na análise dos gráficos gerados para diferentes valores de k usando a técnica t-SNE, observa-se que as conclusões são consistentes com as técnicas anteriores. Nota-se que os clusters para k=2, k=3 e k=6 estão bem separados, sem sobreposição, enquanto para os outros valores de k há sobreposição entre os clusters.
Optamos por utilizar um total de 7 grupos para a clusterização. Agora, nosso foco está em identificar quais variáveis exerceram um papel crucial na diferenciação das observações dentro de cada cluster. Para alcançar esse objetivo, consideraremos os clusters como variáveis de resposta e empregaremos um método de classificação para determinar quais características tiveram uma influência preponderante nessa predição. Escolhemos utilizar o método Random Forest, uma técnica robusta e versátil, que nos permitirá explorar a importância relativa de cada variável na formação dos clusters. Vamos adiante com essa análise detalhada para aprofundar nossa compreensão sobre os padrões subjacentes em nossos dados.
# Passo 1: Criar os clusters com k=6 usando K-Means
k = 7
kmeans = KMeans(n_clusters=k, random_state=42)
clusters = kmeans.fit_predict(X1_scaled_df)
# Dividir os dados em treinamento e teste
X_train, X_test, y_train, y_test = train_test_split(X1_scaled_df,
clusters,
test_size=0.3,
random_state=42)
# Treinar um modelo de classificação multiclasse (Random Forest)
model = RandomForestClassifier(n_estimators=100,
random_state=42)
model.fit(X_train, y_train)
# Fazer previsões no conjunto de teste
y_pred = model.predict(X_test)
# Calcular a precisão do modelo
accuracy = accuracy_score(y_test, y_pred)
print(f"Acurácia do modelo: {accuracy:.2%}")
# Exibir um relatório de classificação
report = classification_report(y_test, y_pred)
print("Relatório de Classificação:\n", report)
Acurácia do modelo: 98.64%
Relatório de Classificação:
precision recall f1-score support
0 0.98 0.98 0.98 368
1 0.98 0.99 0.98 498
2 0.99 1.00 0.99 384
3 1.00 0.99 1.00 487
4 0.95 0.94 0.95 271
5 1.00 1.00 1.00 389
6 0.99 0.99 0.99 474
accuracy 0.99 2871
macro avg 0.98 0.98 0.98 2871
weighted avg 0.99 0.99 0.99 2871
O modelo de classificação alcançou uma excelente acurácia de 98.64%, indicando sua capacidade de classificar corretamente a maioria das observações. As métricas de precisão, recall e F1-score para cada classe também são notáveis, variando de 94% a 100%. Em resumo, o modelo demonstra um desempenho excepcional na classificação das diferentes classes, sendo altamente preciso e abrangente em suas previsões.
Abaixo as variáveis mais importantes do modelo.
# Obter a importância das variáveis
importances = model.feature_importances_
# Criar um DataFrame para visualizar as importâncias
importance_df = pd.DataFrame({'Variável': X1_scaled_df.columns, 'Importância': importances})
# Classificar as variáveis por importância
importance_df = importance_df.sort_values(by='Importância', ascending=False)
# Exibir as variáveis mais importantes
print("Variáveis mais importantes:")
print(importance_df)
Variáveis mais importantes:
Variável Importância
4 MembroAtivo 0.300940
3 TemCartaoCredito 0.284690
2 Produtos 0.153071
1 Idade 0.118386
8 Saldo2 0.102733
5 SalarioEstimado 0.012572
0 PontuacaoCredito 0.011679
7 PontosAcumulados 0.011548
6 IndiceSatisfacao 0.004382
Este estudo buscou, de uma maneira abrangente, a aplicação de um mecanismo de predição de perda de clientes de uma instituição financeira através de técnicas de machine learning e data science. Ao longo das etapas, desde a preparação dos dados até a implementação do algoritmo K-Means e a análise dos resultados, várias considerações e descobertas foram destacadas.
No discorrer do projeto, foram abordadas as variáveis, visando a identificação de dados relevantes e dados descartáveis para calibração do nosso modelo e geração e ganho de efetividade na operação com os dados.
Dentro os modelos da análise, destaca-se a implementação do K-Means resultou em clusters bem definidos, cada um representando grupos naturais e distintos nas observações. A análise da silhueta reforçou a eficácia do modelo na formação desses clusters, enquanto os gráficos t-SNE proporcionaram insights visuais adicionais sobre a separação dos grupos.
Posteriormente, a aplicação do algoritmo Random Forest para classificar os dados nos clusters identificados atingiu uma notável acurácia de 98.64%. Isso indicou que as características identificadas pelo K-Means eram suficientemente discriminativas para permitir a classificação precisa das observações nos grupos.
Finalmente, a avaliação da importância relativa das variáveis revelou que atributos como MembroAtivo, TemCartaoCredito e Produtos desempenharam papéis significativos na formação dos clusters. Essa compreensão é crucial para interpretar os resultados do modelo e extrair insights relevantes para a tomada de decisões.
Para análises futuras, é recomendável uma exploração mais profunda das variáveis, afim de identificar novos padrões, se o modelo definido está calibrado corretamente, além da exploração de outros algoritmos que possa enriquecer ainda mais a compreensão dos dados.